diff --git a/doc/development/indicator.md b/doc/development/indicator.md index dfab564ce..d3469943e 100644 --- a/doc/development/indicator.md +++ b/doc/development/indicator.md @@ -69,23 +69,43 @@ try to create new indicators in `pkg/indicator/` folder, and add compilation hin // go:generate callbackgen -type StructName type StructName struct { ... - UpdateCallbacks []func(value float64) + updateCallbacks []func(value float64) } ``` And implement required interface methods: ```go + +func (inc *StructName) Update(value float64) { + // indicator calculation here... + // push value... +} + +func (inc *StructName) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + // custom function -func (inc *StructName) calculateAndUpdate(kLines []types.KLine) { - // calculation... - // assign the result to calculatedValue - inc.EmitUpdate(calculatedValue) // produce data, broadcast to the subscribers +func (inc *StructName) CalculateAndUpdate(kLines []types.KLine) { + if len(inc.Values) == 0 { + // preload or initialization + for _, k := range allKLines { + inc.PushK(k) + } + + inc.EmitUpdate(inc.Last()) + } else { + // update new value only + k := allKLines[len(allKLines)-1] + inc.PushK(k) + inc.EmitUpdate(calculatedValue) // produce data, broadcast to the subscribers + } } // custom function func (inc *StructName) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { // filter on interval - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } // required diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index 87bea4fe1..4d7ab00e4 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -43,7 +43,7 @@ type StandardIndicatorSet struct { ewma map[types.IntervalWindow]*indicator.EWMA boll map[types.IntervalWindowBandWidth]*indicator.BOLL stoch map[types.IntervalWindow]*indicator.STOCH - volatility map[types.IntervalWindow]*indicator.VOLATILITY + volatility map[types.IntervalWindow]*indicator.Volatility store *MarketDataStore } @@ -55,7 +55,7 @@ func NewStandardIndicatorSet(symbol string, store *MarketDataStore) *StandardInd ewma: make(map[types.IntervalWindow]*indicator.EWMA), boll: make(map[types.IntervalWindowBandWidth]*indicator.BOLL), stoch: make(map[types.IntervalWindow]*indicator.STOCH), - volatility: make(map[types.IntervalWindow]*indicator.VOLATILITY), + volatility: make(map[types.IntervalWindow]*indicator.Volatility), store: store, } @@ -146,10 +146,10 @@ func (set *StandardIndicatorSet) STOCH(iw types.IntervalWindow) *indicator.STOCH } // VOLATILITY returns the volatility(stddev) indicator of the given interval and the window size. -func (set *StandardIndicatorSet) VOLATILITY(iw types.IntervalWindow) *indicator.VOLATILITY { +func (set *StandardIndicatorSet) VOLATILITY(iw types.IntervalWindow) *indicator.Volatility { inc, ok := set.volatility[iw] if !ok { - inc = &indicator.VOLATILITY{IntervalWindow: iw} + inc = &indicator.Volatility{IntervalWindow: iw} inc.Bind(set.store) set.volatility[iw] = inc } diff --git a/pkg/indicator/ad.go b/pkg/indicator/ad.go index 6cdf0e3d9..dc04de306 100644 --- a/pkg/indicator/ad.go +++ b/pkg/indicator/ad.go @@ -59,7 +59,7 @@ func (inc *AD) Length() int { var _ types.SeriesExtend = &AD{} -func (inc *AD) calculateAndUpdate(kLines []types.KLine) { +func (inc *AD) CalculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue @@ -70,12 +70,13 @@ func (inc *AD) calculateAndUpdate(kLines []types.KLine) { inc.EmitUpdate(inc.Last()) inc.EndTime = kLines[len(kLines)-1].EndTime.Time() } + func (inc *AD) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { if inc.Interval != interval { return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *AD) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/alma.go b/pkg/indicator/alma.go index d9a03ad0a..266285269 100644 --- a/pkg/indicator/alma.go +++ b/pkg/indicator/alma.go @@ -75,7 +75,7 @@ func (inc *ALMA) Length() int { var _ types.SeriesExtend = &ALMA{} -func (inc *ALMA) calculateAndUpdate(allKLines []types.KLine) { +func (inc *ALMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.input == nil { for _, k := range allKLines { inc.Update(k.Close.Float64()) @@ -91,7 +91,7 @@ func (inc *ALMA) handleKLineWindowUpdate(interval types.Interval, window types.K if inc.Interval != interval { return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *ALMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/alma_test.go b/pkg/indicator/alma_test.go index c1dc4a2c1..a00d7113d 100644 --- a/pkg/indicator/alma_test.go +++ b/pkg/indicator/alma_test.go @@ -52,7 +52,7 @@ func Test_ALMA(t *testing.T) { Offset: 0.9, Sigma: 6, } - alma.calculateAndUpdate(tt.kLines) + alma.CalculateAndUpdate(tt.kLines) assert.InDelta(t, tt.want, alma.Last(), Delta) assert.InDelta(t, tt.next, alma.Index(1), Delta) assert.Equal(t, tt.all, alma.Length()) diff --git a/pkg/indicator/atr.go b/pkg/indicator/atr.go index 759cffebe..28d6724ab 100644 --- a/pkg/indicator/atr.go +++ b/pkg/indicator/atr.go @@ -20,6 +20,8 @@ type ATR struct { UpdateCallbacks []func(value float64) } +var _ types.SeriesExtend = &ATR{} + func (inc *ATR) Update(high, low, cloze float64) { if inc.Window <= 0 { panic("window must be greater than 0") @@ -72,17 +74,20 @@ func (inc *ATR) Length() int { if inc.RMA == nil { return 0 } + return inc.RMA.Length() } -var _ types.SeriesExtend = &ATR{} +func (inc *ATR) PushK(k types.KLine) { + inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) +} func (inc *ATR) CalculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) + inc.PushK(k) } inc.EmitUpdate(inc.Last()) diff --git a/pkg/indicator/atrp.go b/pkg/indicator/atrp.go index f546e04e2..4d1d377b7 100644 --- a/pkg/indicator/atrp.go +++ b/pkg/indicator/atrp.go @@ -87,12 +87,17 @@ func (inc *ATRP) Length() int { var _ types.SeriesExtend = &ATRP{} +func (inc *ATRP) PushK(k types.KLine) { + inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) +} + func (inc *ATRP) CalculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) + + inc.PushK(k) } inc.EmitUpdate(inc.Last()) diff --git a/pkg/indicator/boll.go b/pkg/indicator/boll.go index 30ebf8466..ebc810fe5 100644 --- a/pkg/indicator/boll.go +++ b/pkg/indicator/boll.go @@ -88,7 +88,7 @@ func (inc *BOLL) LastSMA() float64 { return 0.0 } -func (inc *BOLL) Update(kLines []types.KLine) { +func (inc *BOLL) CalculateAndUpdate(kLines []types.KLine) { if len(kLines) < inc.Window { return } @@ -142,7 +142,7 @@ func (inc *BOLL) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.Update(window) + inc.CalculateAndUpdate(window) } func (inc *BOLL) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/boll_test.go b/pkg/indicator/boll_test.go index 7e64ee11a..2d51e08b1 100644 --- a/pkg/indicator/boll_test.go +++ b/pkg/indicator/boll_test.go @@ -59,7 +59,7 @@ func TestBOLL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { boll := BOLL{IntervalWindow: types.IntervalWindow{Window: tt.window}, K: tt.k} - boll.Update(tt.kLines) + boll.CalculateAndUpdate(tt.kLines) assert.InDelta(t, tt.up, boll.LastUpBand(), Delta) assert.InDelta(t, tt.down, boll.LastDownBand(), Delta) }) diff --git a/pkg/indicator/cci.go b/pkg/indicator/cci.go index ef0ec558d..d7165037a 100644 --- a/pkg/indicator/cci.go +++ b/pkg/indicator/cci.go @@ -3,7 +3,6 @@ package indicator import ( "math" - "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" ) @@ -79,17 +78,20 @@ func (inc *CCI) Length() int { var _ types.SeriesExtend = &CCI{} -var three = fixedpoint.NewFromInt(3) -func (inc *CCI) calculateAndUpdate(allKLines []types.KLine) { +func (inc *CCI) PushK(k types.KLine) { + inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64()) +} + +func (inc *CCI) CalculateAndUpdate(allKLines []types.KLine) { if inc.TypicalPrice.Length() == 0 { for _, k := range allKLines { - inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } else { k := allKLines[len(allKLines)-1] - inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -99,7 +101,7 @@ func (inc *CCI) handleKLineWindowUpdate(interval types.Interval, window types.KL return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *CCI) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/cma.go b/pkg/indicator/cma.go index fbdf35734..d319bd41f 100644 --- a/pkg/indicator/cma.go +++ b/pkg/indicator/cma.go @@ -48,9 +48,13 @@ func (inc *CA) Length() int { var _ types.SeriesExtend = &CA{} -func (inc *CA) calculateAndUpdate(allKLines []types.KLine) { +func (inc *CA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *CA) CalculateAndUpdate(allKLines []types.KLine) { for _, k := range allKLines { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -60,7 +64,7 @@ func (inc *CA) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *CA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/const.go b/pkg/indicator/const.go new file mode 100644 index 000000000..4b48f2b99 --- /dev/null +++ b/pkg/indicator/const.go @@ -0,0 +1,12 @@ +package indicator + +import ( + "time" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +var three = fixedpoint.NewFromInt(3) + +var zeroTime = time.Time{} + diff --git a/pkg/indicator/dema.go b/pkg/indicator/dema.go index d9152d279..69e75c4f2 100644 --- a/pkg/indicator/dema.go +++ b/pkg/indicator/dema.go @@ -50,14 +50,20 @@ func (inc *DEMA) Length() int { var _ types.SeriesExtend = &DEMA{} -func (inc *DEMA) calculateAndUpdate(allKLines []types.KLine) { +func (inc *DEMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *DEMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.a1 == nil { for _, k := range allKLines { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } else { - inc.Update(allKLines[len(allKLines)-1].Close.Float64()) + // last k + k := allKLines[len(allKLines)-1] + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -67,7 +73,7 @@ func (inc *DEMA) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *DEMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/dema_test.go b/pkg/indicator/dema_test.go index c58429672..99da987b9 100644 --- a/pkg/indicator/dema_test.go +++ b/pkg/indicator/dema_test.go @@ -45,7 +45,7 @@ func Test_DEMA(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dema := DEMA{IntervalWindow: types.IntervalWindow{Window: 16}} - dema.calculateAndUpdate(tt.kLines) + dema.CalculateAndUpdate(tt.kLines) last := dema.Last() assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, dema.Index(1), Delta) diff --git a/pkg/indicator/dmi.go b/pkg/indicator/dmi.go index 74ea75eaa..d3352bfc5 100644 --- a/pkg/indicator/dmi.go +++ b/pkg/indicator/dmi.go @@ -15,6 +15,7 @@ import ( //go:generate callbackgen -type DMI type DMI struct { types.IntervalWindow + ADXSmoothing int atr *ATR DMP types.UpdatableSeriesExtend @@ -23,7 +24,8 @@ type DMI struct { DIMinus *types.Queue ADX types.UpdatableSeriesExtend PrevHigh, PrevLow float64 - UpdateCallbacks []func(diplus, diminus, adx float64) + + updateCallbacks []func(diplus, diminus, adx float64) } func (inc *DMI) Update(high, low, cloze float64) { @@ -32,6 +34,7 @@ func (inc *DMI) Update(high, low, cloze float64) { inc.DMN = &RMA{IntervalWindow: inc.IntervalWindow, Adjust: true} inc.ADX = &RMA{IntervalWindow: types.IntervalWindow{Window: inc.ADXSmoothing}, Adjust: true} } + if inc.atr == nil { inc.atr = &ATR{IntervalWindow: inc.IntervalWindow} inc.atr.Update(high, low, cloze) @@ -41,6 +44,7 @@ func (inc *DMI) Update(high, low, cloze float64) { inc.DIMinus = types.NewQueue(500) return } + inc.atr.Update(high, low, cloze) up := high - inc.PrevHigh dn := inc.PrevLow - low @@ -87,15 +91,20 @@ func (inc *DMI) Length() int { return inc.ADX.Length() } -func (inc *DMI) calculateAndUpdate(allKLines []types.KLine) { +func (inc *DMI) PushK(k types.KLine) { + inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) +} + +func (inc *DMI) CalculateAndUpdate(allKLines []types.KLine) { + last := allKLines[len(allKLines)-1] + if inc.ADX == nil { for _, k := range allKLines { - inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.DIPlus.Last(), inc.DIMinus.Last(), inc.ADX.Last()) } } else { - k := allKLines[len(allKLines)-1] - inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) + inc.PushK(last) inc.EmitUpdate(inc.DIPlus.Last(), inc.DIMinus.Last(), inc.ADX.Last()) } } @@ -105,7 +114,7 @@ func (inc *DMI) handleKLineWindowUpdate(interval types.Interval, window types.KL return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *DMI) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/dmi_callbacks.go b/pkg/indicator/dmi_callbacks.go index 93e8dd14d..ed8453907 100644 --- a/pkg/indicator/dmi_callbacks.go +++ b/pkg/indicator/dmi_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *DMI) OnUpdate(cb func(diplus float64, diminus float64, adx float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *DMI) EmitUpdate(diplus float64, diminus float64, adx float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(diplus, diminus, adx) } } diff --git a/pkg/indicator/dmi_test.go b/pkg/indicator/dmi_test.go index 62d2b0aa7..d93e16440 100644 --- a/pkg/indicator/dmi_test.go +++ b/pkg/indicator/dmi_test.go @@ -77,7 +77,7 @@ func Test_DMI(t *testing.T) { IntervalWindow: types.IntervalWindow{Window: 5}, ADXSmoothing: 14, } - dmi.calculateAndUpdate(tt.klines) + dmi.CalculateAndUpdate(tt.klines) assert.InDelta(t, dmi.GetDIPlus().Last(), tt.want.dip, Delta) assert.InDelta(t, dmi.GetDIMinus().Last(), tt.want.dim, Delta) assert.InDelta(t, dmi.GetADX().Last(), tt.want.adx, Delta) diff --git a/pkg/indicator/drift.go b/pkg/indicator/drift.go index 8494acc39..f0cc1f757 100644 --- a/pkg/indicator/drift.go +++ b/pkg/indicator/drift.go @@ -68,14 +68,19 @@ func (inc *Drift) Length() int { var _ types.SeriesExtend = &Drift{} -func (inc *Drift) calculateAndUpdate(allKLines []types.KLine) { +func (inc *Drift) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *Drift) CalculateAndUpdate(allKLines []types.KLine) { if inc.chng == nil { for _, k := range allKLines { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } else { - inc.Update(allKLines[len(allKLines)-1].Close.Float64()) + k := allKLines[len(allKLines)-1] + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -85,7 +90,7 @@ func (inc *Drift) handleKLineWindowUpdate(interval types.Interval, window types. return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *Drift) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/drift_test.go b/pkg/indicator/drift_test.go index 38d6a732a..9318da9d6 100644 --- a/pkg/indicator/drift_test.go +++ b/pkg/indicator/drift_test.go @@ -30,7 +30,7 @@ func Test_Drift(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { drift := Drift{IntervalWindow: types.IntervalWindow{Window: 3}} - drift.calculateAndUpdate(tt.kLines) + drift.CalculateAndUpdate(tt.kLines) assert.Equal(t, drift.Length(), tt.all) for _, v := range drift.Values { assert.LessOrEqual(t, v, 1.0) diff --git a/pkg/indicator/emv.go b/pkg/indicator/emv.go index f626ad70e..a2e1eb359 100644 --- a/pkg/indicator/emv.go +++ b/pkg/indicator/emv.go @@ -63,17 +63,21 @@ func (inc *EMV) Length() int { var _ types.SeriesExtend = &EMV{} -func (inc *EMV) calculateAndUpdate(allKLines []types.KLine) { +func (inc *EMV) PushK(k types.KLine) { + inc.Update(k.High.Float64(), k.Low.Float64(), k.Volume.Float64()) +} + +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()) + inc.PushK(k) 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.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -82,7 +86,7 @@ func (inc *EMV) handleKLineWindowUpdate(interval types.Interval, window types.KL if inc.Interval != interval { return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *EMV) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/emv_test.go b/pkg/indicator/emv_test.go index fd9054f5f..03b57a760 100644 --- a/pkg/indicator/emv_test.go +++ b/pkg/indicator/emv_test.go @@ -16,7 +16,7 @@ func Test_EMV(t *testing.T) { } emv.Update(63.74, 62.63, 32178836) emv.Update(64.51, 63.85, 36461672) - assert.InDelta(t, 1.8, emv.Values.Cache.Last(), Delta) + assert.InDelta(t, 1.8, emv.Values.rawValues.Last(), Delta) emv.Update(64.57, 63.81, 51372680) emv.Update(64.31, 62.62, 42476356) emv.Update(63.43, 62.73, 29504176) diff --git a/pkg/indicator/ewma.go b/pkg/indicator/ewma.go index 4335c1316..c09941534 100644 --- a/pkg/indicator/ewma.go +++ b/pkg/indicator/ewma.go @@ -1,11 +1,8 @@ package indicator import ( - "math" "time" - log "github.com/sirupsen/logrus" - "github.com/c9s/bbgo/pkg/types" ) @@ -17,12 +14,15 @@ const MaxNumOfEWMATruncateSize = 100 type EWMA struct { types.IntervalWindow types.SeriesBase + Values types.Float64Slice LastOpenTime time.Time - UpdateCallbacks []func(value float64) + updateCallbacks []func(value float64) } +var _ types.SeriesExtend = &EWMA{} + func (inc *EWMA) Update(value float64) { var multiplier = 2.0 / float64(1+inc.Window) @@ -58,57 +58,35 @@ func (inc *EWMA) Length() int { return len(inc.Values) } -func (inc *EWMA) calculateAndUpdate(allKLines []types.KLine) { - if len(allKLines) < inc.Window { - // we can't calculate +func (inc *EWMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) + inc.LastOpenTime = k.StartTime.Time() +} + +func (inc *EWMA) CalculateAndUpdate(allKLines []types.KLine) { + if len(inc.Values) == 0 { + for _, k := range allKLines { + inc.PushK(k) + } + + inc.EmitUpdate(inc.Last()) + } else { + k := allKLines[len(allKLines)-1] + inc.PushK(k) + inc.EmitUpdate(inc.Last()) + } +} + +func (inc *EWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { return } - var priceF = KLineClosePriceMapper - var dataLen = len(allKLines) - var multiplier = 2.0 / (float64(inc.Window) + 1) + inc.CalculateAndUpdate(window) +} - // init the values fromNthK the kline data - var fromNthK = 1 - if len(inc.Values) == 0 { - // for the first value, we should use the close price - inc.Values = []float64{priceF(allKLines[0])} - } else { - if len(inc.Values) >= MaxNumOfEWMA { - inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] - } - - fromNthK = len(inc.Values) - - // update ewma with the existing values - for i := dataLen - 1; i > 0; i-- { - var k = allKLines[i] - if k.StartTime.After(inc.LastOpenTime) { - fromNthK = i - } else { - break - } - } - } - - for i := fromNthK; i < dataLen; i++ { - var k = allKLines[i] - var ewma = priceF(k)*multiplier + (1-multiplier)*inc.Values[i-1] - inc.Values.Push(ewma) - inc.LastOpenTime = k.StartTime.Time() - inc.EmitUpdate(ewma) - } - - if len(inc.Values) != dataLen { - // check error - log.Warnf("%s EMA (%d) value length (%d) != kline window length (%d)", inc.Interval, inc.Window, len(inc.Values), dataLen) - } - - v1 := math.Floor(inc.Values[len(inc.Values)-1]*100.0) / 100.0 - v2 := math.Floor(CalculateKLinesEMA(allKLines, priceF, inc.Window)*100.0) / 100.0 - if v1 != v2 { - log.Warnf("ACCUMULATED %s EMA (%d) %f != EMA %f", inc.Interval, inc.Window, v1, v2) - } +func (inc *EWMA) Bind(updater KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) } func CalculateKLinesEMA(allKLines []types.KLine, priceF KLinePriceMapper, window int) float64 { @@ -125,17 +103,3 @@ func ewma(prices []float64, multiplier float64) float64 { return prices[end]*multiplier + (1-multiplier)*ewma(prices[:end], multiplier) } - -func (inc *EWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { - if inc.Interval != interval { - return - } - - inc.calculateAndUpdate(window) -} - -func (inc *EWMA) Bind(updater KLineWindowUpdater) { - updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) -} - -var _ types.SeriesExtend = &EWMA{} diff --git a/pkg/indicator/ewma_callbacks.go b/pkg/indicator/ewma_callbacks.go index a0458ee7c..38fbacb26 100644 --- a/pkg/indicator/ewma_callbacks.go +++ b/pkg/indicator/ewma_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *EWMA) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *EWMA) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/hull.go b/pkg/indicator/hull.go index 7eaf8ad70..de907d6b0 100644 --- a/pkg/indicator/hull.go +++ b/pkg/indicator/hull.go @@ -16,7 +16,7 @@ type HULL struct { ma2 *EWMA result *EWMA - UpdateCallbacks []func(value float64) + updateCallbacks []func(value float64) } func (inc *HULL) Update(value float64) { @@ -55,7 +55,7 @@ func (inc *HULL) Length() int { var _ types.SeriesExtend = &HULL{} // TODO: should we just ignore the possible overlapping? -func (inc *HULL) calculateAndUpdate(allKLines []types.KLine) { +func (inc *HULL) CalculateAndUpdate(allKLines []types.KLine) { doable := false if inc.ma1 == nil || inc.ma1.Length() == 0 { doable = true @@ -76,7 +76,7 @@ func (inc *HULL) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *HULL) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/hull_callbacks.go b/pkg/indicator/hull_callbacks.go index aa95c8dd9..5f6222f84 100644 --- a/pkg/indicator/hull_callbacks.go +++ b/pkg/indicator/hull_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *HULL) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *HULL) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/hull_test.go b/pkg/indicator/hull_test.go index 95f883cd8..64472c0e6 100644 --- a/pkg/indicator/hull_test.go +++ b/pkg/indicator/hull_test.go @@ -45,7 +45,7 @@ func Test_HULL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hull := HULL{IntervalWindow: types.IntervalWindow{Window: 16}} - hull.calculateAndUpdate(tt.kLines) + hull.CalculateAndUpdate(tt.kLines) last := hull.Last() assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, hull.Index(1), Delta) diff --git a/pkg/indicator/inf.go b/pkg/indicator/inf.go new file mode 100644 index 000000000..c93de779f --- /dev/null +++ b/pkg/indicator/inf.go @@ -0,0 +1,27 @@ +package indicator + +import "github.com/c9s/bbgo/pkg/types" + +type KLineWindowUpdater interface { + OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow)) +} + +type KLineClosedEmitter interface { + OnKLineClosed(func(k types.KLine)) +} + +// KLinePusher provides an interface for API user to push kline value to the indicator. +// The indicator implements its own way to calculate the value from the given kline object. +type KLinePusher interface { + PushK(k types.KLine) +} + +// KLineLoader provides an interface for API user to load history klines to the indicator. +// The indicator implements its own way to calculate the values from the given history kline array. +type KLineLoader interface { + LoadK(allKLines []types.KLine) +} + +type KLineCalculateUpdater interface { + CalculateAndUpdate(allKLines []types.KLine) +} diff --git a/pkg/indicator/line.go b/pkg/indicator/line.go index a3932ac1c..edb8276f1 100644 --- a/pkg/indicator/line.go +++ b/pkg/indicator/line.go @@ -22,11 +22,12 @@ type Line struct { Interval types.Interval } -func (l *Line) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { +func (l *Line) handleKLineWindowUpdate(interval types.Interval, allKLines types.KLineWindow) { if interval != l.Interval { return } - newTime := window.Last().EndTime.Time() + + newTime := allKLines.Last().EndTime.Time() delta := int(newTime.Sub(l.currentTime).Minutes()) / l.Interval.Minutes() l.startIndex += delta l.endIndex += delta diff --git a/pkg/indicator/macd.go b/pkg/indicator/macd.go index 43e94589b..81e7a0f70 100644 --- a/pkg/indicator/macd.go +++ b/pkg/indicator/macd.go @@ -11,6 +11,7 @@ macd implements moving average convergence divergence indicator Moving Average Convergence Divergence (MACD) - https://www.investopedia.com/terms/m/macd.asp +- https://school.stockcharts.com/doku.php?id=technical_indicators:macd-histogram */ //go:generate callbackgen -type MACD @@ -19,21 +20,21 @@ type MACD struct { ShortPeriod int // 12 LongPeriod int // 26 Values types.Float64Slice - FastEWMA EWMA - SlowEWMA EWMA - SignalLine EWMA + FastEWMA *EWMA + SlowEWMA *EWMA + SignalLine *EWMA Histogram types.Float64Slice EndTime time.Time - UpdateCallbacks []func(value float64) + updateCallbacks []func(value float64) } func (inc *MACD) Update(x float64) { if len(inc.Values) == 0 { - inc.FastEWMA = EWMA{IntervalWindow: types.IntervalWindow{Window: inc.ShortPeriod}} - inc.SlowEWMA = EWMA{IntervalWindow: types.IntervalWindow{Window: inc.LongPeriod}} - inc.SignalLine = EWMA{IntervalWindow: types.IntervalWindow{Window: inc.Window}} + inc.FastEWMA = &EWMA{IntervalWindow: types.IntervalWindow{Window: inc.ShortPeriod}} + inc.SlowEWMA = &EWMA{IntervalWindow: types.IntervalWindow{Window: inc.LongPeriod}} + inc.SignalLine = &EWMA{IntervalWindow: types.IntervalWindow{Window: inc.Window}} } // update fast and slow ema @@ -51,27 +52,38 @@ func (inc *MACD) Update(x float64) { inc.Histogram.Push(macd - inc.SignalLine.Last()) } -func (inc *MACD) calculateMACD(kLines []types.KLine, priceF KLinePriceMapper) float64 { - for _, kline := range kLines { - inc.Update(kline.Close.Float64()) +func (inc *MACD) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 } + return inc.Values[len(inc.Values)-1] } -func (inc *MACD) calculateAndUpdate(kLines []types.KLine) { - if len(kLines) == 0 { +func (inc *MACD) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *MACD) CalculateAndUpdate(allKLines []types.KLine) { + if len(allKLines) == 0 { return } - for _, k := range kLines { - if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { - continue + last := allKLines[len(allKLines)-1] + if len(inc.Values) == 0 { + for _, k := range allKLines { + if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { + continue + } + + inc.PushK(k) } - inc.Update(k.Close.Float64()) + } else { + inc.PushK(last) } - inc.EmitUpdate(inc.Values[len(inc.Values)-1]) - inc.EndTime = kLines[len(kLines)-1].EndTime.Time() + inc.EmitUpdate(inc.Last()) + inc.EndTime = last.EndTime.Time() } func (inc *MACD) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { @@ -79,7 +91,7 @@ func (inc *MACD) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *MACD) Bind(updater KLineWindowUpdater) { @@ -95,6 +107,7 @@ func (inc *MACDValues) Last() float64 { if len(inc.Values) == 0 { return 0.0 } + return inc.Values[len(inc.Values)-1] } @@ -103,6 +116,7 @@ func (inc *MACDValues) Index(i int) float64 { if length == 0 || length-1-i < 0 { return 0.0 } + return inc.Values[length-1+i] } @@ -117,5 +131,5 @@ func (inc *MACD) MACD() types.SeriesExtend { } func (inc *MACD) Singals() types.SeriesExtend { - return &inc.SignalLine + return inc.SignalLine } diff --git a/pkg/indicator/macd_callbacks.go b/pkg/indicator/macd_callbacks.go index a368fa625..cd8b1a8f6 100644 --- a/pkg/indicator/macd_callbacks.go +++ b/pkg/indicator/macd_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *MACD) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *MACD) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/macd_test.go b/pkg/indicator/macd_test.go index 6cf074fff..0088f9db9 100644 --- a/pkg/indicator/macd_test.go +++ b/pkg/indicator/macd_test.go @@ -41,8 +41,9 @@ func Test_calculateMACD(t *testing.T) { t.Run(tt.name, func(t *testing.T) { iw := types.IntervalWindow{Window: 9} macd := MACD{IntervalWindow: iw, ShortPeriod: 12, LongPeriod: 26} - priceF := KLineClosePriceMapper - got := macd.calculateMACD(tt.kLines, priceF) + macd.CalculateAndUpdate(tt.kLines) + + got := macd.Last() diff := math.Trunc((got-tt.want)*100) / 100 if diff != 0 { t.Errorf("calculateMACD() = %v, want %v", got, tt.want) diff --git a/pkg/indicator/mapper.go b/pkg/indicator/mapper.go new file mode 100644 index 000000000..b101e62ae --- /dev/null +++ b/pkg/indicator/mapper.go @@ -0,0 +1,33 @@ +package indicator + +import "github.com/c9s/bbgo/pkg/types" + +type KLinePriceMapper func(k types.KLine) float64 + +func KLineOpenPriceMapper(k types.KLine) float64 { + return k.Open.Float64() +} + +func KLineClosePriceMapper(k types.KLine) float64 { + return k.Close.Float64() +} + +func KLineTypicalPriceMapper(k types.KLine) float64 { + return (k.High.Float64() + k.Low.Float64() + k.Close.Float64()) / 3. +} + +func KLinePriceVolumeMapper(k types.KLine) float64 { + return k.Close.Mul(k.Volume).Float64() +} + +func KLineVolumeMapper(k types.KLine) float64 { + return k.Volume.Float64() +} + +func MapKLinePrice(kLines []types.KLine, f KLinePriceMapper) (prices []float64) { + for _, k := range kLines { + prices = append(prices, f(k)) + } + + return prices +} diff --git a/pkg/indicator/obv.go b/pkg/indicator/obv.go index 52321892f..3fd663063 100644 --- a/pkg/indicator/obv.go +++ b/pkg/indicator/obv.go @@ -18,9 +18,9 @@ type OBV struct { types.IntervalWindow Values types.Float64Slice PrePrice float64 + EndTime time.Time - EndTime time.Time - UpdateCallbacks []func(value float64) + updateCallbacks []func(value float64) } func (inc *OBV) Update(price, volume float64) { @@ -54,13 +54,19 @@ func (inc *OBV) Index(i int) float64 { var _ types.SeriesExtend = &OBV{} -func (inc *OBV) calculateAndUpdate(kLines []types.KLine) { +func (inc *OBV) PushK(k types.KLine) { + inc.Update(k.Close.Float64(), k.Volume.Float64()) +} + +func (inc *OBV) CalculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k.Close.Float64(), k.Volume.Float64()) + + inc.PushK(k) } + inc.EmitUpdate(inc.Last()) inc.EndTime = kLines[len(kLines)-1].EndTime.Time() } @@ -70,7 +76,7 @@ func (inc *OBV) handleKLineWindowUpdate(interval types.Interval, window types.KL return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *OBV) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/obv_callbacks.go b/pkg/indicator/obv_callbacks.go index b0897152c..2b1ce69b1 100644 --- a/pkg/indicator/obv_callbacks.go +++ b/pkg/indicator/obv_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *OBV) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *OBV) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/obv_test.go b/pkg/indicator/obv_test.go index 66d951a29..a70a0be4b 100644 --- a/pkg/indicator/obv_test.go +++ b/pkg/indicator/obv_test.go @@ -51,7 +51,7 @@ func Test_calculateOBV(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { obv := OBV{IntervalWindow: types.IntervalWindow{Window: tt.window}} - obv.calculateAndUpdate(tt.kLines) + obv.CalculateAndUpdate(tt.kLines) assert.Equal(t, len(obv.Values), len(tt.want)) for i, v := range obv.Values { assert.InDelta(t, v, tt.want[i], Delta) diff --git a/pkg/indicator/pivot.go b/pkg/indicator/pivot.go index ccc1322e2..e3912afea 100644 --- a/pkg/indicator/pivot.go +++ b/pkg/indicator/pivot.go @@ -38,7 +38,7 @@ func (inc *Pivot) LastHigh() float64 { return inc.Highs[len(inc.Highs)-1] } -func (inc *Pivot) Update(klines []types.KLine) { +func (inc *Pivot) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -84,7 +84,7 @@ func (inc *Pivot) handleKLineWindowUpdate(interval types.Interval, window types. return } - inc.Update(window) + inc.CalculateAndUpdate(window) } func (inc *Pivot) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/rma.go b/pkg/indicator/rma.go index 857261ecc..caa39b507 100644 --- a/pkg/indicator/rma.go +++ b/pkg/indicator/rma.go @@ -13,13 +13,16 @@ import ( type RMA struct { types.SeriesBase types.IntervalWindow - Values types.Float64Slice - counter int - Adjust bool - tmp float64 - sum float64 - EndTime time.Time - UpdateCallbacks []func(value float64) + + Values types.Float64Slice + EndTime time.Time + + counter int + Adjust bool + tmp float64 + sum float64 + + updateCallbacks []func(value float64) } func (inc *RMA) Update(x float64) { @@ -64,23 +67,35 @@ func (inc *RMA) Length() int { var _ types.SeriesExtend = &RMA{} -func (inc *RMA) calculateAndUpdate(kLines []types.KLine) { - for _, k := range kLines { - if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { - continue +func (inc *RMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *RMA) CalculateAndUpdate(kLines []types.KLine) { + last := kLines[len(kLines)-1] + + if len(inc.Values) == 0 { + for _, k := range kLines { + if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { + continue + } + + inc.PushK(k) } - inc.Update(k.Close.Float64()) + } else { + inc.PushK(last) } inc.EmitUpdate(inc.Last()) - inc.EndTime = kLines[len(kLines)-1].EndTime.Time() + inc.EndTime = last.EndTime.Time() } + func (inc *RMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { if inc.Interval != interval { return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *RMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/rma_callbacks.go b/pkg/indicator/rma_callbacks.go index f5a40ca5e..e08b30668 100644 --- a/pkg/indicator/rma_callbacks.go +++ b/pkg/indicator/rma_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *RMA) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *RMA) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/rsi.go b/pkg/indicator/rsi.go index 8a76322ab..286b7b16e 100644 --- a/pkg/indicator/rsi.go +++ b/pkg/indicator/rsi.go @@ -22,7 +22,7 @@ type RSI struct { PreviousAvgGain float64 EndTime time.Time - UpdateCallbacks []func(value float64) + updateCallbacks []func(value float64) } func (inc *RSI) Update(price float64) { @@ -80,12 +80,17 @@ func (inc *RSI) Length() int { var _ types.SeriesExtend = &RSI{} -func (inc *RSI) calculateAndUpdate(kLines []types.KLine) { +func (inc *RSI) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *RSI) CalculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k.Close.Float64()) + + inc.PushK(k) } inc.EmitUpdate(inc.Last()) @@ -97,7 +102,7 @@ func (inc *RSI) handleKLineWindowUpdate(interval types.Interval, window types.KL return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *RSI) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/rsi_callbacks.go b/pkg/indicator/rsi_callbacks.go index 2c1a11f66..ea795930e 100644 --- a/pkg/indicator/rsi_callbacks.go +++ b/pkg/indicator/rsi_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *RSI) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *RSI) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/rsi_test.go b/pkg/indicator/rsi_test.go index 80e4c9187..3b96fd433 100644 --- a/pkg/indicator/rsi_test.go +++ b/pkg/indicator/rsi_test.go @@ -59,7 +59,7 @@ func Test_calculateRSI(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rsi := RSI{IntervalWindow: types.IntervalWindow{Window: tt.window}} - rsi.calculateAndUpdate(tt.kLines) + rsi.CalculateAndUpdate(tt.kLines) assert.Equal(t, len(rsi.Values), len(tt.want)) for i, v := range rsi.Values { assert.InDelta(t, v, tt.want[i], Delta) diff --git a/pkg/indicator/sma.go b/pkg/indicator/sma.go index c07f3293f..7f5fa65b9 100644 --- a/pkg/indicator/sma.go +++ b/pkg/indicator/sma.go @@ -10,15 +10,13 @@ import ( const MaxNumOfSMA = 5_000 const MaxNumOfSMATruncateSize = 100 -var zeroTime time.Time - //go:generate callbackgen -type SMA type SMA struct { types.SeriesBase types.IntervalWindow - Values types.Float64Slice - Cache *types.Queue - EndTime time.Time + Values types.Float64Slice + rawValues *types.Queue + EndTime time.Time UpdateCallbacks []func(value float64) } @@ -45,37 +43,39 @@ func (inc *SMA) Length() int { var _ types.SeriesExtend = &SMA{} func (inc *SMA) Update(value float64) { - if inc.Cache == nil { - inc.Cache = types.NewQueue(inc.Window) + if inc.rawValues == nil { + inc.rawValues = types.NewQueue(inc.Window) inc.SeriesBase.Series = inc } - inc.Cache.Update(value) - if inc.Cache.Length() < inc.Window { + + inc.rawValues.Update(value) + if inc.rawValues.Length() < inc.Window { return } - inc.Values.Push(types.Mean(inc.Cache)) - if inc.Values.Length() > MaxNumOfSMA { - inc.Values = inc.Values[MaxNumOfSMATruncateSize-1:] - } + + inc.Values.Push(types.Mean(inc.rawValues)) } -func (inc *SMA) calculateAndUpdate(kLines []types.KLine) { - var index = len(kLines) - 1 - var kline = kLines[index] - if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) { - return - } - if inc.Cache == nil { - for _, k := range kLines { - inc.Update(KLineClosePriceMapper(k)) - inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Values.Last()) +func (inc *SMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) + inc.EndTime = k.EndTime.Time() +} + +func (inc *SMA) CalculateAndUpdate(allKLines []types.KLine) { + var last = allKLines[len(allKLines)-1] + + if inc.rawValues == nil { + for _, k := range allKLines { + if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) { + continue + } + inc.PushK(k) } } else { - inc.Update(KLineClosePriceMapper(kline)) - inc.EndTime = kline.EndTime.Time() - inc.EmitUpdate(inc.Values.Last()) + inc.PushK(last) } + + inc.EmitUpdate(inc.Values.Last()) } func (inc *SMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { @@ -83,7 +83,7 @@ func (inc *SMA) handleKLineWindowUpdate(interval types.Interval, window types.KL return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *SMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/sma_test.go b/pkg/indicator/sma_test.go index e385e7d68..fb40716cc 100644 --- a/pkg/indicator/sma_test.go +++ b/pkg/indicator/sma_test.go @@ -52,7 +52,7 @@ func Test_SMA(t *testing.T) { sma := SMA{ IntervalWindow: types.IntervalWindow{Window: 5}, } - sma.calculateAndUpdate(tt.kLines) + sma.CalculateAndUpdate(tt.kLines) assert.InDelta(t, tt.want, sma.Last(), Delta) assert.InDelta(t, tt.next, sma.Index(1), Delta) sma.Update(tt.update) diff --git a/pkg/indicator/ssf.go b/pkg/indicator/ssf.go index 28871a041..a055dd059 100644 --- a/pkg/indicator/ssf.go +++ b/pkg/indicator/ssf.go @@ -93,14 +93,19 @@ func (inc *SSF) Last() float64 { var _ types.SeriesExtend = &SSF{} -func (inc *SSF) calculateAndUpdate(allKLines []types.KLine) { +func (inc *SSF) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *SSF) CalculateAndUpdate(allKLines []types.KLine) { if inc.Values != nil { - inc.Update(allKLines[len(allKLines)-1].Close.Float64()) + k := allKLines[len(allKLines)-1] + inc.PushK(k) inc.EmitUpdate(inc.Last()) return } for _, k := range allKLines { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -109,7 +114,7 @@ func (inc *SSF) handleKLineWindowUpdate(interval types.Interval, window types.KL if inc.Interval != interval { return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *SSF) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/ssf_test.go b/pkg/indicator/ssf_test.go index 0eced9ca9..253d72220 100644 --- a/pkg/indicator/ssf_test.go +++ b/pkg/indicator/ssf_test.go @@ -62,7 +62,7 @@ func Test_SSF(t *testing.T) { IntervalWindow: types.IntervalWindow{Window: 5}, Poles: tt.poles, } - ssf.calculateAndUpdate(tt.kLines) + ssf.CalculateAndUpdate(tt.kLines) assert.InDelta(t, tt.want, ssf.Last(), Delta) assert.InDelta(t, tt.next, ssf.Index(1), Delta) assert.Equal(t, tt.all, ssf.Length()) diff --git a/pkg/indicator/stoch.go b/pkg/indicator/stoch.go index c24cd8169..602df765f 100644 --- a/pkg/indicator/stoch.go +++ b/pkg/indicator/stoch.go @@ -59,7 +59,11 @@ func (inc *STOCH) LastD() float64 { return inc.D[len(inc.D)-1] } -func (inc *STOCH) calculateAndUpdate(kLines []types.KLine) { +func (inc *STOCH) PushK(k types.KLine) { + inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) +} + +func (inc *STOCH) CalculateAndUpdate(kLines []types.KLine) { if len(kLines) < inc.Window || len(kLines) < DPeriod { return } @@ -68,7 +72,8 @@ func (inc *STOCH) calculateAndUpdate(kLines []types.KLine) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) + + inc.PushK(k) } inc.EmitUpdate(inc.LastK(), inc.LastD()) @@ -80,7 +85,7 @@ func (inc *STOCH) handleKLineWindowUpdate(interval types.Interval, window types. return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *STOCH) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/stoch_test.go b/pkg/indicator/stoch_test.go index f8a90bfee..56839dadc 100644 --- a/pkg/indicator/stoch_test.go +++ b/pkg/indicator/stoch_test.go @@ -56,7 +56,7 @@ func TestSTOCH_update(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { kd := STOCH{IntervalWindow: types.IntervalWindow{Window: tt.window}} - kd.calculateAndUpdate(tt.kLines) + kd.CalculateAndUpdate(tt.kLines) got_k := kd.LastK() diff_k := math.Trunc((got_k-tt.want_k)*100) / 100 diff --git a/pkg/indicator/supertrend.go b/pkg/indicator/supertrend.go index d87b41b59..a25858cac 100644 --- a/pkg/indicator/supertrend.go +++ b/pkg/indicator/supertrend.go @@ -1,10 +1,11 @@ package indicator import ( - "github.com/sirupsen/logrus" "math" "time" + "github.com/sirupsen/logrus" + "github.com/c9s/bbgo/pkg/types" ) @@ -50,6 +51,7 @@ func (inc *Supertrend) Index(i int) float64 { func (inc *Supertrend) Length() int { return len(inc.trendPrices) } + func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) { if inc.Window <= 0 { panic("window must be greater than 0") @@ -127,12 +129,17 @@ func (inc *Supertrend) GetSignal() types.Direction { var _ types.SeriesExtend = &Supertrend{} -func (inc *Supertrend) calculateAndUpdate(kLines []types.KLine) { +func (inc *Supertrend) PushK(k types.KLine) { + inc.Update(k.GetHigh().Float64(), k.GetLow().Float64(), k.GetClose().Float64()) +} + +func (inc *Supertrend) CalculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k.GetHigh().Float64(), k.GetLow().Float64(), k.GetClose().Float64()) + + inc.PushK(k) } inc.EmitUpdate(inc.Last()) @@ -144,7 +151,7 @@ func (inc *Supertrend) handleKLineWindowUpdate(interval types.Interval, window t return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *Supertrend) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/tema.go b/pkg/indicator/tema.go index 8d1fc3fd3..37a9f78b0 100644 --- a/pkg/indicator/tema.go +++ b/pkg/indicator/tema.go @@ -55,14 +55,19 @@ func (inc *TEMA) Length() int { var _ types.SeriesExtend = &TEMA{} -func (inc *TEMA) calculateAndUpdate(allKLines []types.KLine) { +func (inc *TEMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *TEMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.A1 == nil { for _, k := range allKLines { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } else { - inc.Update(allKLines[len(allKLines)-1].Close.Float64()) + k := allKLines[len(allKLines)-1] + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -72,7 +77,7 @@ func (inc *TEMA) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *TEMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/tema_test.go b/pkg/indicator/tema_test.go index 641153f40..b50c72ca7 100644 --- a/pkg/indicator/tema_test.go +++ b/pkg/indicator/tema_test.go @@ -46,7 +46,7 @@ func Test_TEMA(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tema := TEMA{IntervalWindow: types.IntervalWindow{Window: 16}} - tema.calculateAndUpdate(tt.kLines) + tema.CalculateAndUpdate(tt.kLines) last := tema.Last() assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, tema.Index(1), Delta) diff --git a/pkg/indicator/till.go b/pkg/indicator/till.go index 795194e5c..3fdc2a706 100644 --- a/pkg/indicator/till.go +++ b/pkg/indicator/till.go @@ -12,18 +12,19 @@ const defaultVolumeFactor = 0.7 type TILL struct { types.SeriesBase types.IntervalWindow - VolumeFactor float64 - e1 *EWMA - e2 *EWMA - e3 *EWMA - e4 *EWMA - e5 *EWMA - e6 *EWMA - c1 float64 - c2 float64 - c3 float64 - c4 float64 - UpdateCallbacks []func(value float64) + VolumeFactor float64 + e1 *EWMA + e2 *EWMA + e3 *EWMA + e4 *EWMA + e5 *EWMA + e6 *EWMA + c1 float64 + c2 float64 + c3 float64 + c4 float64 + + updateCallbacks []func(value float64) } func (inc *TILL) Update(value float64) { @@ -85,7 +86,11 @@ func (inc *TILL) Length() int { var _ types.Series = &TILL{} -func (inc *TILL) calculateAndUpdate(allKLines []types.KLine) { +func (inc *TILL) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *TILL) CalculateAndUpdate(allKLines []types.KLine) { doable := false if inc.e1 == nil { doable = true @@ -94,8 +99,9 @@ func (inc *TILL) calculateAndUpdate(allKLines []types.KLine) { if !doable && k.StartTime.After(inc.e1.LastOpenTime) { doable = true } + if doable { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -106,7 +112,7 @@ func (inc *TILL) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *TILL) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/till_callbacks.go b/pkg/indicator/till_callbacks.go index 53d89cb8d..d17a8dcd9 100644 --- a/pkg/indicator/till_callbacks.go +++ b/pkg/indicator/till_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *TILL) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *TILL) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/till_test.go b/pkg/indicator/till_test.go index 4615a5dbe..1b03017b8 100644 --- a/pkg/indicator/till_test.go +++ b/pkg/indicator/till_test.go @@ -4,9 +4,10 @@ import ( "encoding/json" "testing" + "github.com/stretchr/testify/assert" + "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" - "github.com/stretchr/testify/assert" ) /* @@ -55,7 +56,7 @@ func Test_TILL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { till := TILL{IntervalWindow: types.IntervalWindow{Window: 16}} - till.calculateAndUpdate(tt.kLines) + till.CalculateAndUpdate(tt.kLines) last := till.Last() assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, till.Index(1), Delta) diff --git a/pkg/indicator/tma.go b/pkg/indicator/tma.go index c100a987d..31600f2c4 100644 --- a/pkg/indicator/tma.go +++ b/pkg/indicator/tma.go @@ -50,14 +50,19 @@ func (inc *TMA) Length() int { var _ types.SeriesExtend = &TMA{} -func (inc *TMA) calculateAndUpdate(allKLines []types.KLine) { +func (inc *TMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *TMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.s1 == nil { for _, k := range allKLines { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } else { - inc.Update(allKLines[len(allKLines)-1].Close.Float64()) + k := allKLines[len(allKLines)-1] + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -67,7 +72,7 @@ func (inc *TMA) handleKLineWindowUpdate(interval types.Interval, window types.KL return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *TMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/util.go b/pkg/indicator/util.go index 05f4c6a69..d676406cf 100644 --- a/pkg/indicator/util.go +++ b/pkg/indicator/util.go @@ -1,29 +1,2 @@ package indicator -import "github.com/c9s/bbgo/pkg/types" - -type KLinePriceMapper func(k types.KLine) float64 - -func KLineOpenPriceMapper(k types.KLine) float64 { - return k.Open.Float64() -} - -func KLineClosePriceMapper(k types.KLine) float64 { - return k.Close.Float64() -} - -func KLineTypicalPriceMapper(k types.KLine) float64 { - return (k.High.Float64() + k.Low.Float64() + k.Close.Float64()) / 3. -} - -func MapKLinePrice(kLines []types.KLine, f KLinePriceMapper) (prices []float64) { - for _, k := range kLines { - prices = append(prices, f(k)) - } - - return prices -} - -type KLineWindowUpdater interface { - OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow)) -} diff --git a/pkg/indicator/vidya.go b/pkg/indicator/vidya.go index cda286e00..f29769c49 100644 --- a/pkg/indicator/vidya.go +++ b/pkg/indicator/vidya.go @@ -15,7 +15,7 @@ type VIDYA struct { Values types.Float64Slice input types.Float64Slice - UpdateCallbacks []func(value float64) + updateCallbacks []func(value float64) } func (inc *VIDYA) Update(value float64) { @@ -70,14 +70,19 @@ func (inc *VIDYA) Length() int { var _ types.SeriesExtend = &VIDYA{} -func (inc *VIDYA) calculateAndUpdate(allKLines []types.KLine) { +func (inc *VIDYA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *VIDYA) CalculateAndUpdate(allKLines []types.KLine) { if inc.input.Length() == 0 { for _, k := range allKLines { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } else { - inc.Update(allKLines[len(allKLines)-1].Close.Float64()) + k := allKLines[len(allKLines)-1] + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -87,7 +92,7 @@ func (inc *VIDYA) handleKLineWindowUpdate(interval types.Interval, window types. return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *VIDYA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/vidya_callbacks.go b/pkg/indicator/vidya_callbacks.go index b78e797c4..c05d0a20b 100644 --- a/pkg/indicator/vidya_callbacks.go +++ b/pkg/indicator/vidya_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *VIDYA) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *VIDYA) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/volatility.go b/pkg/indicator/volatility.go index 9f4571408..9d86c7f31 100644 --- a/pkg/indicator/volatility.go +++ b/pkg/indicator/volatility.go @@ -13,10 +13,10 @@ import ( const MaxNumOfVOL = 5_000 const MaxNumOfVOLTruncateSize = 100 -//var zeroTime time.Time +// var zeroTime time.Time -//go:generate callbackgen -type VOLATILITY -type VOLATILITY struct { +//go:generate callbackgen -type Volatility +type Volatility struct { types.SeriesBase types.IntervalWindow Values types.Float64Slice @@ -25,42 +25,43 @@ type VOLATILITY struct { UpdateCallbacks []func(value float64) } -func (inc *VOLATILITY) Last() float64 { +func (inc *Volatility) Last() float64 { if len(inc.Values) == 0 { return 0.0 } return inc.Values[len(inc.Values)-1] } -func (inc *VOLATILITY) Index(i int) float64 { +func (inc *Volatility) Index(i int) float64 { if len(inc.Values)-i <= 0 { return 0.0 } return inc.Values[len(inc.Values)-i-1] } -func (inc *VOLATILITY) Length() int { +func (inc *Volatility) Length() int { return len(inc.Values) } -var _ types.SeriesExtend = &VOLATILITY{} +var _ types.SeriesExtend = &Volatility{} -func (inc *VOLATILITY) calculateAndUpdate(klines []types.KLine) { - if len(klines) < inc.Window { +func (inc *Volatility) CalculateAndUpdate(allKLines []types.KLine) { + if len(allKLines) < inc.Window { return } - var end = len(klines) - 1 - var lastKLine = klines[end] + var end = len(allKLines) - 1 + var lastKLine = allKLines[end] if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { return } + if len(inc.Values) == 0 { inc.SeriesBase.Series = inc } - var recentT = klines[end-(inc.Window-1) : end+1] + var recentT = allKLines[end-(inc.Window-1) : end+1] volatility, err := calculateVOLATILITY(recentT, inc.Window, KLineClosePriceMapper) if err != nil { @@ -73,20 +74,20 @@ func (inc *VOLATILITY) calculateAndUpdate(klines []types.KLine) { inc.Values = inc.Values[MaxNumOfVOLTruncateSize-1:] } - inc.EndTime = klines[end].GetEndTime().Time() + inc.EndTime = allKLines[end].GetEndTime().Time() inc.EmitUpdate(volatility) } -func (inc *VOLATILITY) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { +func (inc *Volatility) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { if inc.Interval != interval { return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } -func (inc *VOLATILITY) Bind(updater KLineWindowUpdater) { +func (inc *Volatility) Bind(updater KLineWindowUpdater) { updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) } diff --git a/pkg/indicator/volatility_callbacks.go b/pkg/indicator/volatility_callbacks.go index 9f5311d75..c04211a08 100644 --- a/pkg/indicator/volatility_callbacks.go +++ b/pkg/indicator/volatility_callbacks.go @@ -1,14 +1,14 @@ -// Code generated by "callbackgen -type VOLATILITY"; DO NOT EDIT. +// Code generated by "callbackgen -type Volatility"; DO NOT EDIT. package indicator import () -func (inc *VOLATILITY) OnUpdate(cb func(value float64)) { +func (inc *Volatility) OnUpdate(cb func(value float64)) { inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) } -func (inc *VOLATILITY) EmitUpdate(value float64) { +func (inc *Volatility) EmitUpdate(value float64) { for _, cb := range inc.UpdateCallbacks { cb(value) } diff --git a/pkg/indicator/vwap.go b/pkg/indicator/vwap.go index 89e3b28e9..a95cee206 100644 --- a/pkg/indicator/vwap.go +++ b/pkg/indicator/vwap.go @@ -71,18 +71,21 @@ func (inc *VWAP) Length() int { var _ types.SeriesExtend = &VWAP{} -func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) { - var priceF = KLineTypicalPriceMapper +func (inc *VWAP) PushK(k types.KLine) { + inc.Update(KLineTypicalPriceMapper(k), k.Volume.Float64()) +} - for _, k := range kLines { +func (inc *VWAP) CalculateAndUpdate(allKLines []types.KLine) { + for _, k := range allKLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(priceF(k), k.Volume.Float64()) + + inc.PushK(k) } inc.EmitUpdate(inc.Last()) - inc.EndTime = kLines[len(kLines)-1].EndTime.Time() + inc.EndTime = allKLines[len(allKLines)-1].EndTime.Time() } func (inc *VWAP) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { @@ -90,14 +93,14 @@ func (inc *VWAP) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *VWAP) Bind(updater KLineWindowUpdater) { updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) } -func CalculateVWAP(klines []types.KLine, priceF KLinePriceMapper, window int) float64 { +func calculateVWAP(klines []types.KLine, priceF KLinePriceMapper, window int) float64 { vwap := VWAP{IntervalWindow: types.IntervalWindow{Window: window}} for _, k := range klines { vwap.Update(priceF(k), k.Volume.Float64()) diff --git a/pkg/indicator/vwap_callbacks.go b/pkg/indicator/vwap_callbacks.go index 9a235d17a..918ddcf50 100644 --- a/pkg/indicator/vwap_callbacks.go +++ b/pkg/indicator/vwap_callbacks.go @@ -4,12 +4,12 @@ package indicator import () -func (V *VWAP) OnUpdate(cb func(value float64)) { - V.UpdateCallbacks = append(V.UpdateCallbacks, cb) +func (inc *VWAP) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) } -func (V *VWAP) EmitUpdate(value float64) { - for _, cb := range V.UpdateCallbacks { +func (inc *VWAP) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { cb(value) } } diff --git a/pkg/indicator/vwap_test.go b/pkg/indicator/vwap_test.go index d168bb938..7929b4bba 100644 --- a/pkg/indicator/vwap_test.go +++ b/pkg/indicator/vwap_test.go @@ -64,7 +64,7 @@ func Test_calculateVWAP(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { priceF := KLineTypicalPriceMapper - got := CalculateVWAP(tt.kLines, priceF, tt.window) + got := calculateVWAP(tt.kLines, priceF, tt.window) diff := math.Trunc((got-tt.want)*100) / 100 if diff != 0 { t.Errorf("calculateVWAP() = %v, want %v", got, tt.want) diff --git a/pkg/indicator/vwma.go b/pkg/indicator/vwma.go index 4ee1068c9..742a37aca 100644 --- a/pkg/indicator/vwma.go +++ b/pkg/indicator/vwma.go @@ -49,27 +49,20 @@ func (inc *VWMA) Length() int { var _ types.SeriesExtend = &VWMA{} -func KLinePriceVolumeMapper(k types.KLine) float64 { - return k.Close.Mul(k.Volume).Float64() -} -func KLineVolumeMapper(k types.KLine) float64 { - return k.Volume.Float64() -} - -func (inc *VWMA) calculateAndUpdate(kLines []types.KLine) { - if len(kLines) < inc.Window { +func (inc *VWMA) CalculateAndUpdate(allKLines []types.KLine) { + if len(allKLines) < inc.Window { return } - var index = len(kLines) - 1 - var kline = kLines[index] + var index = len(allKLines) - 1 + var kline = allKLines[index] if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) { return } - var recentK = kLines[index-(inc.Window-1) : index+1] + var recentK = allKLines[index-(inc.Window-1) : index+1] pv, err := calculateSMA(recentK, inc.Window, KLinePriceVolumeMapper) if err != nil { @@ -93,7 +86,7 @@ func (inc *VWMA) calculateAndUpdate(kLines []types.KLine) { inc.Values = inc.Values[MaxNumOfSMATruncateSize-1:] } - inc.EndTime = kLines[index].EndTime.Time() + inc.EndTime = allKLines[index].EndTime.Time() inc.EmitUpdate(vwma) } @@ -103,7 +96,7 @@ func (inc *VWMA) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *VWMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/wwma.go b/pkg/indicator/wwma.go index be0ec0a7e..bc5ab5a67 100644 --- a/pkg/indicator/wwma.go +++ b/pkg/indicator/wwma.go @@ -1,8 +1,9 @@ package indicator import ( - "github.com/c9s/bbgo/pkg/types" "time" + + "github.com/c9s/bbgo/pkg/types" ) // Refer: Welles Wilder's Moving Average @@ -56,7 +57,11 @@ func (inc *WWMA) Length() int { return len(inc.Values) } -func (inc *WWMA) calculateAndUpdate(allKLines []types.KLine) { +func (inc *WWMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *WWMA) CalculateAndUpdate(allKLines []types.KLine) { if len(allKLines) < inc.Window { // we can't calculate return @@ -68,7 +73,7 @@ func (inc *WWMA) calculateAndUpdate(allKLines []types.KLine) { doable = true } if doable { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.LastOpenTime = k.StartTime.Time() inc.EmitUpdate(inc.Last()) } @@ -80,7 +85,7 @@ func (inc *WWMA) handleKLineWindowUpdate(interval types.Interval, window types.K return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *WWMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/zlema.go b/pkg/indicator/zlema.go index f127c0008..cb7dfbde3 100644 --- a/pkg/indicator/zlema.go +++ b/pkg/indicator/zlema.go @@ -16,7 +16,7 @@ type ZLEMA struct { zlema *EWMA lag int - UpdateCallbacks []func(value float64) + updateCallbacks []func(value float64) } func (inc *ZLEMA) Index(i int) float64 { @@ -59,14 +59,19 @@ func (inc *ZLEMA) Update(value float64) { var _ types.SeriesExtend = &ZLEMA{} -func (inc *ZLEMA) calculateAndUpdate(allKLines []types.KLine) { +func (inc *ZLEMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) +} + +func (inc *ZLEMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.zlema == nil { for _, k := range allKLines { - inc.Update(k.Close.Float64()) + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } else { - inc.Update(allKLines[len(allKLines)-1].Close.Float64()) + k := allKLines[len(allKLines)-1] + inc.PushK(k) inc.EmitUpdate(inc.Last()) } } @@ -76,7 +81,7 @@ func (inc *ZLEMA) handleKLineWindowUpdate(interval types.Interval, window types. return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *ZLEMA) Bind(updater KLineWindowUpdater) { diff --git a/pkg/indicator/zlema_callbacks.go b/pkg/indicator/zlema_callbacks.go index d70147699..98a84c659 100644 --- a/pkg/indicator/zlema_callbacks.go +++ b/pkg/indicator/zlema_callbacks.go @@ -5,11 +5,11 @@ package indicator import () func (inc *ZLEMA) OnUpdate(cb func(value float64)) { - inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) + inc.updateCallbacks = append(inc.updateCallbacks, cb) } func (inc *ZLEMA) EmitUpdate(value float64) { - for _, cb := range inc.UpdateCallbacks { + for _, cb := range inc.updateCallbacks { cb(value) } } diff --git a/pkg/indicator/zlema_test.go b/pkg/indicator/zlema_test.go index 4b0e546ab..4560f4276 100644 --- a/pkg/indicator/zlema_test.go +++ b/pkg/indicator/zlema_test.go @@ -45,7 +45,7 @@ func Test_ZLEMA(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { zlema := ZLEMA{IntervalWindow: types.IntervalWindow{Window: 16}} - zlema.calculateAndUpdate(tt.kLines) + zlema.CalculateAndUpdate(tt.kLines) last := zlema.Last() assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, zlema.Index(1), Delta) diff --git a/pkg/strategy/factorzoo/correlation.go b/pkg/strategy/factorzoo/correlation.go index 6e666d8fa..7c094d58b 100644 --- a/pkg/strategy/factorzoo/correlation.go +++ b/pkg/strategy/factorzoo/correlation.go @@ -29,7 +29,7 @@ func (inc *Correlation) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *Correlation) calculateAndUpdate(klines []types.KLine) { +func (inc *Correlation) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -64,7 +64,7 @@ func (inc *Correlation) handleKLineWindowUpdate(interval types.Interval, window return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *Correlation) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/A18.go b/pkg/strategy/fmaker/A18.go index e0c456a21..71a671bbb 100644 --- a/pkg/strategy/fmaker/A18.go +++ b/pkg/strategy/fmaker/A18.go @@ -26,7 +26,7 @@ func (inc *A18) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *A18) calculateAndUpdate(klines []types.KLine) { +func (inc *A18) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *A18) handleKLineWindowUpdate(interval types.Interval, window types.KL return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *A18) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/A2.go b/pkg/strategy/fmaker/A2.go index 8f7239259..4946bc28d 100644 --- a/pkg/strategy/fmaker/A2.go +++ b/pkg/strategy/fmaker/A2.go @@ -26,7 +26,7 @@ func (inc *A2) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *A2) calculateAndUpdate(klines []types.KLine) { +func (inc *A2) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *A2) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *A2) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/A3.go b/pkg/strategy/fmaker/A3.go index 35e0cc3b8..e08b6bf60 100644 --- a/pkg/strategy/fmaker/A3.go +++ b/pkg/strategy/fmaker/A3.go @@ -27,7 +27,7 @@ func (inc *A3) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *A3) calculateAndUpdate(klines []types.KLine) { +func (inc *A3) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -63,7 +63,7 @@ func (inc *A3) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *A3) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/A34.go b/pkg/strategy/fmaker/A34.go index 5062c1317..ba5484485 100644 --- a/pkg/strategy/fmaker/A34.go +++ b/pkg/strategy/fmaker/A34.go @@ -26,7 +26,7 @@ func (inc *A34) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *A34) calculateAndUpdate(klines []types.KLine) { +func (inc *A34) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *A34) handleKLineWindowUpdate(interval types.Interval, window types.KL return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *A34) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/R.go b/pkg/strategy/fmaker/R.go index 278dfdcaa..4647ab153 100644 --- a/pkg/strategy/fmaker/R.go +++ b/pkg/strategy/fmaker/R.go @@ -30,7 +30,7 @@ func (inc *R) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *R) calculateAndUpdate(klines []types.KLine) { +func (inc *R) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -66,7 +66,7 @@ func (inc *R) handleKLineWindowUpdate(interval types.Interval, window types.KLin return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *R) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/S0.go b/pkg/strategy/fmaker/S0.go index 78ea60522..2f1c943bb 100644 --- a/pkg/strategy/fmaker/S0.go +++ b/pkg/strategy/fmaker/S0.go @@ -26,7 +26,7 @@ func (inc *S0) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *S0) calculateAndUpdate(klines []types.KLine) { +func (inc *S0) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *S0) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *S0) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/S1.go b/pkg/strategy/fmaker/S1.go index 85fdac13d..75d0888d5 100644 --- a/pkg/strategy/fmaker/S1.go +++ b/pkg/strategy/fmaker/S1.go @@ -25,7 +25,7 @@ func (inc *S1) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *S1) calculateAndUpdate(klines []types.KLine) { +func (inc *S1) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -60,7 +60,7 @@ func (inc *S1) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *S1) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/S2.go b/pkg/strategy/fmaker/S2.go index b52f49c11..7d21fea7f 100644 --- a/pkg/strategy/fmaker/S2.go +++ b/pkg/strategy/fmaker/S2.go @@ -25,7 +25,7 @@ func (inc *S2) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *S2) calculateAndUpdate(klines []types.KLine) { +func (inc *S2) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -60,7 +60,7 @@ func (inc *S2) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *S2) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/S3.go b/pkg/strategy/fmaker/S3.go index bd585d48c..195db9bc4 100644 --- a/pkg/strategy/fmaker/S3.go +++ b/pkg/strategy/fmaker/S3.go @@ -26,7 +26,7 @@ func (inc *S3) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *S3) calculateAndUpdate(klines []types.KLine) { +func (inc *S3) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *S3) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *S3) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/S4.go b/pkg/strategy/fmaker/S4.go index 5e204dc0d..d2953304f 100644 --- a/pkg/strategy/fmaker/S4.go +++ b/pkg/strategy/fmaker/S4.go @@ -26,7 +26,7 @@ func (inc *S4) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *S4) calculateAndUpdate(klines []types.KLine) { +func (inc *S4) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *S4) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *S4) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/S5.go b/pkg/strategy/fmaker/S5.go index 0cc4c54b8..82c117e64 100644 --- a/pkg/strategy/fmaker/S5.go +++ b/pkg/strategy/fmaker/S5.go @@ -26,7 +26,7 @@ func (inc *S5) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *S5) calculateAndUpdate(klines []types.KLine) { +func (inc *S5) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *S5) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *S5) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/S6.go b/pkg/strategy/fmaker/S6.go index e4db9e4f2..2b6ab4ead 100644 --- a/pkg/strategy/fmaker/S6.go +++ b/pkg/strategy/fmaker/S6.go @@ -26,7 +26,7 @@ func (inc *S6) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *S6) calculateAndUpdate(klines []types.KLine) { +func (inc *S6) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *S6) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *S6) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/fmaker/S7.go b/pkg/strategy/fmaker/S7.go index d5f0b5f70..2431f7edc 100644 --- a/pkg/strategy/fmaker/S7.go +++ b/pkg/strategy/fmaker/S7.go @@ -26,7 +26,7 @@ func (inc *S7) Last() float64 { return inc.Values[len(inc.Values)-1] } -func (inc *S7) calculateAndUpdate(klines []types.KLine) { +func (inc *S7) CalculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return } @@ -62,7 +62,7 @@ func (inc *S7) handleKLineWindowUpdate(interval types.Interval, window types.KLi return } - inc.calculateAndUpdate(window) + inc.CalculateAndUpdate(window) } func (inc *S7) Bind(updater indicator.KLineWindowUpdater) { diff --git a/pkg/strategy/pivotshort/strategy.go b/pkg/strategy/pivotshort/strategy.go index 1b843fd4b..8756cbd5d 100644 --- a/pkg/strategy/pivotshort/strategy.go +++ b/pkg/strategy/pivotshort/strategy.go @@ -296,7 +296,7 @@ func preloadPivot(pivot *indicator.Pivot, store *bbgo.MarketDataStore) *types.KL log.Debugf("updating pivot indicator: %d klines", len(*klines)) for i := pivot.Window; i < len(*klines); i++ { - pivot.Update((*klines)[0 : i+1]) + pivot.CalculateAndUpdate((*klines)[0 : i+1]) } log.Debugf("found %v previous lows: %v", pivot.IntervalWindow, pivot.Lows) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index aca499413..5c5c8aa78 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -693,7 +693,9 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order if store, ok := s.sourceSession.MarketDataStore(s.Symbol); ok { if klines, ok2 := store.KLinesOfInterval(s.BollBandInterval); ok2 { - s.boll.Update(*klines) + for i := 0; i < len(*klines); i++ { + s.boll.CalculateAndUpdate((*klines)[0 : i+1]) + } } }