Merge pull request #645 from zenixls2/feature/emv

feature: add emv indicator, fix: sma
This commit is contained in:
Yo-An Lin 2022-05-31 17:20:57 +08:00 committed by GitHub
commit d409c41a29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 145 additions and 3 deletions

88
pkg/indicator/emv.go Normal file
View 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)
}

View 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
View 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)
}

View File

@ -18,6 +18,7 @@ var zeroTime time.Time
type SMA struct { type SMA struct {
types.IntervalWindow types.IntervalWindow
Values types.Float64Slice Values types.Float64Slice
Cache types.Float64Slice
EndTime time.Time EndTime time.Time
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
@ -46,11 +47,15 @@ func (inc *SMA) Length() int {
var _ types.Series = &SMA{} var _ types.Series = &SMA{}
func (inc *SMA) Update(value float64) { func (inc *SMA) Update(value float64) {
length := len(inc.Values) if len(inc.Cache) < inc.Window {
if length == 0 { inc.Cache = append(inc.Cache, value)
inc.Values = append(inc.Values, value) if len(inc.Cache) == inc.Window {
return 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) newVal := (inc.Values[length-1]*float64(inc.Window-1) + value) / float64(inc.Window)
inc.Values = append(inc.Values, newVal) inc.Values = append(inc.Values, newVal)
} }