mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +00:00
Merge pull request #645 from zenixls2/feature/emv
feature: add emv indicator, fix: sma
This commit is contained in:
commit
d409c41a29
88
pkg/indicator/emv.go
Normal file
88
pkg/indicator/emv.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package indicator
|
||||
|
||||
import (
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
// Refer: Ease of Movement
|
||||
// Refer URL: https://www.investopedia.com/terms/e/easeofmovement.asp
|
||||
|
||||
//go:generate callbackgen -type EMV
|
||||
type EMV struct {
|
||||
types.IntervalWindow
|
||||
prevH float64
|
||||
prevL float64
|
||||
Values *SMA
|
||||
EMVScale float64
|
||||
|
||||
UpdateCallbacks []func(value float64)
|
||||
}
|
||||
|
||||
const DefaultEMVScale float64 = 100000000.
|
||||
|
||||
func (inc *EMV) Update(high, low, vol float64) {
|
||||
if inc.EMVScale == 0 {
|
||||
inc.EMVScale = DefaultEMVScale
|
||||
}
|
||||
if inc.prevH == 0 || inc.Values == nil {
|
||||
inc.prevH = high
|
||||
inc.prevL = low
|
||||
inc.Values = &SMA{IntervalWindow: inc.IntervalWindow}
|
||||
return
|
||||
}
|
||||
distanceMoved := (high+low)/2. - (inc.prevH+inc.prevL)/2.
|
||||
boxRatio := vol / inc.EMVScale / (high - low)
|
||||
result := distanceMoved / boxRatio
|
||||
inc.prevH = high
|
||||
inc.prevL = low
|
||||
inc.Values.Update(result)
|
||||
}
|
||||
|
||||
func (inc *EMV) Index(i int) float64 {
|
||||
if inc.Values == nil {
|
||||
return 0
|
||||
}
|
||||
return inc.Values.Index(i)
|
||||
}
|
||||
|
||||
func (inc *EMV) Last() float64 {
|
||||
if inc.Values == nil {
|
||||
return 0
|
||||
}
|
||||
return inc.Values.Last()
|
||||
}
|
||||
|
||||
func (inc *EMV) Length() int {
|
||||
if inc.Values == nil {
|
||||
return 0
|
||||
}
|
||||
return inc.Values.Length()
|
||||
}
|
||||
|
||||
var _ types.Series = &EMV{}
|
||||
|
||||
func (inc *EMV) calculateAndUpdate(allKLines []types.KLine) {
|
||||
if inc.Values == nil {
|
||||
for _, k := range allKLines {
|
||||
inc.Update(k.High.Float64(), k.Low.Float64(), k.Volume.Float64())
|
||||
if inc.Length() > 0 {
|
||||
inc.EmitUpdate(inc.Last())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
k := allKLines[len(allKLines)-1]
|
||||
inc.Update(k.High.Float64(), k.Low.Float64(), k.Volume.Float64())
|
||||
inc.EmitUpdate(inc.Last())
|
||||
}
|
||||
}
|
||||
|
||||
func (inc *EMV) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
|
||||
if inc.Interval != interval {
|
||||
return
|
||||
}
|
||||
inc.calculateAndUpdate(window)
|
||||
}
|
||||
|
||||
func (inc *EMV) Bind(updater KLineWindowUpdater) {
|
||||
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
||||
}
|
15
pkg/indicator/emv_callbacks.go
Normal file
15
pkg/indicator/emv_callbacks.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Code generated by "callbackgen -type EMV"; DO NOT EDIT.
|
||||
|
||||
package indicator
|
||||
|
||||
import ()
|
||||
|
||||
func (inc *EMV) OnUpdate(cb func(value float64)) {
|
||||
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
|
||||
}
|
||||
|
||||
func (inc *EMV) EmitUpdate(value float64) {
|
||||
for _, cb := range inc.UpdateCallbacks {
|
||||
cb(value)
|
||||
}
|
||||
}
|
34
pkg/indicator/emv_test.go
Normal file
34
pkg/indicator/emv_test.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package indicator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// data from https://school.stockcharts.com/doku.php?id=technical_indicators:ease_of_movement_emv
|
||||
func Test_EMV(t *testing.T) {
|
||||
var Delta = 0.01
|
||||
emv := &EMV{
|
||||
EMVScale: 100000000,
|
||||
IntervalWindow: types.IntervalWindow{Window: 14},
|
||||
}
|
||||
emv.Update(63.74, 62.63, 32178836)
|
||||
emv.Update(64.51, 63.85, 36461672)
|
||||
assert.InDelta(t, 1.8, emv.Values.Cache.Last(), Delta)
|
||||
emv.Update(64.57, 63.81, 51372680)
|
||||
emv.Update(64.31, 62.62, 42476356)
|
||||
emv.Update(63.43, 62.73, 29504176)
|
||||
emv.Update(62.85, 61.95, 33098600)
|
||||
emv.Update(62.70, 62.06, 30577960)
|
||||
emv.Update(63.18, 62.69, 35693928)
|
||||
emv.Update(62.47, 61.54, 49768136)
|
||||
emv.Update(64.16, 63.21, 44759968)
|
||||
emv.Update(64.38, 63.87, 33425504)
|
||||
emv.Update(64.89, 64.29, 15895085)
|
||||
emv.Update(65.25, 64.48, 37015388)
|
||||
emv.Update(64.69, 63.65, 40672116)
|
||||
emv.Update(64.26, 63.68, 35627200)
|
||||
assert.InDelta(t, -0.03, emv.Last(), Delta)
|
||||
}
|
|
@ -18,6 +18,7 @@ var zeroTime time.Time
|
|||
type SMA struct {
|
||||
types.IntervalWindow
|
||||
Values types.Float64Slice
|
||||
Cache types.Float64Slice
|
||||
EndTime time.Time
|
||||
|
||||
UpdateCallbacks []func(value float64)
|
||||
|
@ -46,11 +47,15 @@ func (inc *SMA) Length() int {
|
|||
var _ types.Series = &SMA{}
|
||||
|
||||
func (inc *SMA) Update(value float64) {
|
||||
length := len(inc.Values)
|
||||
if length == 0 {
|
||||
inc.Values = append(inc.Values, value)
|
||||
if len(inc.Cache) < inc.Window {
|
||||
inc.Cache = append(inc.Cache, value)
|
||||
if len(inc.Cache) == inc.Window {
|
||||
inc.Values = append(inc.Values, types.Mean(&inc.Cache))
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
length := len(inc.Values)
|
||||
newVal := (inc.Values[length-1]*float64(inc.Window-1) + value) / float64(inc.Window)
|
||||
inc.Values = append(inc.Values, newVal)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user