Skip to content

Commit

Permalink
feature: add emv indicator, fix: sma
Browse files Browse the repository at this point in the history
  • Loading branch information
zenixls2 committed May 31, 2022
1 parent 8652b4e commit a2a186c
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 3 deletions.
88 changes: 88 additions & 0 deletions pkg/indicator/emv.go
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions pkg/indicator/emv_callbacks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions pkg/indicator/emv_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
11 changes: 8 additions & 3 deletions pkg/indicator/sma.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit a2a186c

Please sign in to comment.