indicator: clean up and update calculator method names

This commit is contained in:
c9s 2022-07-14 01:12:36 +08:00
parent c27f416dbc
commit 2a3118a086
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
44 changed files with 291 additions and 173 deletions

View File

@ -43,7 +43,7 @@ type StandardIndicatorSet struct {
ewma map[types.IntervalWindow]*indicator.EWMA ewma map[types.IntervalWindow]*indicator.EWMA
boll map[types.IntervalWindowBandWidth]*indicator.BOLL boll map[types.IntervalWindowBandWidth]*indicator.BOLL
stoch map[types.IntervalWindow]*indicator.STOCH stoch map[types.IntervalWindow]*indicator.STOCH
volatility map[types.IntervalWindow]*indicator.VOLATILITY volatility map[types.IntervalWindow]*indicator.Volatility
store *MarketDataStore store *MarketDataStore
} }
@ -55,7 +55,7 @@ func NewStandardIndicatorSet(symbol string, store *MarketDataStore) *StandardInd
ewma: make(map[types.IntervalWindow]*indicator.EWMA), ewma: make(map[types.IntervalWindow]*indicator.EWMA),
boll: make(map[types.IntervalWindowBandWidth]*indicator.BOLL), boll: make(map[types.IntervalWindowBandWidth]*indicator.BOLL),
stoch: make(map[types.IntervalWindow]*indicator.STOCH), stoch: make(map[types.IntervalWindow]*indicator.STOCH),
volatility: make(map[types.IntervalWindow]*indicator.VOLATILITY), volatility: make(map[types.IntervalWindow]*indicator.Volatility),
store: store, 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. // 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] inc, ok := set.volatility[iw]
if !ok { if !ok {
inc = &indicator.VOLATILITY{IntervalWindow: iw} inc = &indicator.Volatility{IntervalWindow: iw}
inc.Bind(set.store) inc.Bind(set.store)
set.volatility[iw] = inc set.volatility[iw] = inc
} }

View File

@ -3,7 +3,6 @@ package indicator
import ( import (
"math" "math"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
@ -79,17 +78,20 @@ func (inc *CCI) Length() int {
var _ types.SeriesExtend = &CCI{} 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 { if inc.TypicalPrice.Length() == 0 {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} else { } else {
k := allKLines[len(allKLines)-1] 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()) inc.EmitUpdate(inc.Last())
} }
} }
@ -99,7 +101,7 @@ func (inc *CCI) handleKLineWindowUpdate(interval types.Interval, window types.KL
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *CCI) Bind(updater KLineWindowUpdater) { func (inc *CCI) Bind(updater KLineWindowUpdater) {

View File

@ -48,9 +48,13 @@ func (inc *CA) Length() int {
var _ types.SeriesExtend = &CA{} var _ types.SeriesExtend = &CA{}
func (inc *CA) calculateAndUpdate(allKLines []types.KLine) { func (inc *CA) PushK(k types.KLine) {
for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.Update(k.Close.Float64())
}
func (inc *CA) CalculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines {
inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
@ -60,7 +64,7 @@ func (inc *CA) handleKLineWindowUpdate(interval types.Interval, window types.KLi
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *CA) Bind(updater KLineWindowUpdater) { func (inc *CA) Bind(updater KLineWindowUpdater) {

12
pkg/indicator/const.go Normal file
View File

@ -0,0 +1,12 @@
package indicator
import (
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
var three = fixedpoint.NewFromInt(3)
var zeroTime = time.Time{}

View File

@ -50,14 +50,20 @@ func (inc *DEMA) Length() int {
var _ types.SeriesExtend = &DEMA{} var _ types.SeriesExtend = &DEMA{}
func (inc *DEMA) PushK(k types.KLine) {
inc.Update(k.Close.Float64())
}
func (inc *DEMA) calculateAndUpdate(allKLines []types.KLine) { func (inc *DEMA) calculateAndUpdate(allKLines []types.KLine) {
if inc.a1 == nil { if inc.a1 == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} else { } else {
inc.Update(allKLines[len(allKLines)-1].Close.Float64()) // last k
k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }

View File

@ -68,14 +68,19 @@ func (inc *Drift) Length() int {
var _ types.SeriesExtend = &Drift{} 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 { if inc.chng == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} else { } else {
inc.Update(allKLines[len(allKLines)-1].Close.Float64()) k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
@ -85,7 +90,7 @@ func (inc *Drift) handleKLineWindowUpdate(interval types.Interval, window types.
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *Drift) Bind(updater KLineWindowUpdater) { func (inc *Drift) Bind(updater KLineWindowUpdater) {

View File

@ -30,7 +30,7 @@ func Test_Drift(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
drift := Drift{IntervalWindow: types.IntervalWindow{Window: 3}} drift := Drift{IntervalWindow: types.IntervalWindow{Window: 3}}
drift.calculateAndUpdate(tt.kLines) drift.CalculateAndUpdate(tt.kLines)
assert.Equal(t, drift.Length(), tt.all) assert.Equal(t, drift.Length(), tt.all)
for _, v := range drift.Values { for _, v := range drift.Values {
assert.LessOrEqual(t, v, 1.0) assert.LessOrEqual(t, v, 1.0)

View File

@ -63,17 +63,21 @@ func (inc *EMV) Length() int {
var _ types.SeriesExtend = &EMV{} 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 { if inc.Values == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.High.Float64(), k.Low.Float64(), k.Volume.Float64()) inc.PushK(k)
if inc.Length() > 0 { if inc.Length() > 0 {
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.Update(k.High.Float64(), k.Low.Float64(), k.Volume.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
@ -82,7 +86,7 @@ func (inc *EMV) handleKLineWindowUpdate(interval types.Interval, window types.KL
if inc.Interval != interval { if inc.Interval != interval {
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *EMV) Bind(updater KLineWindowUpdater) { func (inc *EMV) Bind(updater KLineWindowUpdater) {

View File

@ -9,3 +9,13 @@ type KLineWindowUpdater interface {
type KLineCloseHandler interface { type KLineCloseHandler interface {
OnKLineClosed(func(k types.KLine)) 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)
}
type KLineCalculator interface {
CalculateAndUpdate(allKLines []types.KLine)
}

View File

@ -22,11 +22,12 @@ type Line struct {
Interval types.Interval 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 { if interval != l.Interval {
return return
} }
newTime := window.Last().EndTime.Time()
newTime := allKLines.Last().EndTime.Time()
delta := int(newTime.Sub(l.currentTime).Minutes()) / l.Interval.Minutes() delta := int(newTime.Sub(l.currentTime).Minutes()) / l.Interval.Minutes()
l.startIndex += delta l.startIndex += delta
l.endIndex += delta l.endIndex += delta

View File

@ -51,14 +51,20 @@ func (inc *MACD) Update(x float64) {
inc.Histogram.Push(macd - inc.SignalLine.Last()) inc.Histogram.Push(macd - inc.SignalLine.Last())
} }
// Deprecated -- this function is not used ??? ask @narumi
func (inc *MACD) calculateMACD(kLines []types.KLine, priceF KLinePriceMapper) float64 { func (inc *MACD) calculateMACD(kLines []types.KLine, priceF KLinePriceMapper) float64 {
for _, kline := range kLines { for _, k := range kLines {
inc.Update(kline.Close.Float64()) inc.PushK(k)
} }
return inc.Values[len(inc.Values)-1] return inc.Values[len(inc.Values)-1]
} }
func (inc *MACD) calculateAndUpdate(kLines []types.KLine) { func (inc *MACD) PushK(k types.KLine) {
inc.Update(k.Close.Float64())
}
func (inc *MACD) CalculateAndUpdate(kLines []types.KLine) {
if len(kLines) == 0 { if len(kLines) == 0 {
return return
} }
@ -67,7 +73,8 @@ func (inc *MACD) calculateAndUpdate(kLines []types.KLine) {
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
continue continue
} }
inc.Update(k.Close.Float64())
inc.PushK(k)
} }
inc.EmitUpdate(inc.Values[len(inc.Values)-1]) inc.EmitUpdate(inc.Values[len(inc.Values)-1])
@ -79,7 +86,7 @@ func (inc *MACD) handleKLineWindowUpdate(interval types.Interval, window types.K
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *MACD) Bind(updater KLineWindowUpdater) { func (inc *MACD) Bind(updater KLineWindowUpdater) {

33
pkg/indicator/mapper.go Normal file
View File

@ -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
}

View File

@ -54,13 +54,19 @@ func (inc *OBV) Index(i int) float64 {
var _ types.SeriesExtend = &OBV{} 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 { for _, k := range kLines {
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
continue continue
} }
inc.Update(k.Close.Float64(), k.Volume.Float64())
inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
inc.EndTime = kLines[len(kLines)-1].EndTime.Time() inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
} }
@ -70,7 +76,7 @@ func (inc *OBV) handleKLineWindowUpdate(interval types.Interval, window types.KL
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *OBV) Bind(updater KLineWindowUpdater) { func (inc *OBV) Bind(updater KLineWindowUpdater) {

View File

@ -51,7 +51,7 @@ func Test_calculateOBV(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
obv := OBV{IntervalWindow: types.IntervalWindow{Window: tt.window}} 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)) assert.Equal(t, len(obv.Values), len(tt.want))
for i, v := range obv.Values { for i, v := range obv.Values {
assert.InDelta(t, v, tt.want[i], Delta) assert.InDelta(t, v, tt.want[i], Delta)

View File

@ -38,7 +38,7 @@ func (inc *Pivot) LastHigh() float64 {
return inc.Highs[len(inc.Highs)-1] 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 { if len(klines) < inc.Window {
return return
} }
@ -84,7 +84,7 @@ func (inc *Pivot) handleKLineWindowUpdate(interval types.Interval, window types.
return return
} }
inc.Update(window) inc.CalculateAndUpdate(window)
} }
func (inc *Pivot) Bind(updater KLineWindowUpdater) { func (inc *Pivot) Bind(updater KLineWindowUpdater) {

View File

@ -64,12 +64,17 @@ func (inc *RMA) Length() int {
var _ types.SeriesExtend = &RMA{} var _ types.SeriesExtend = &RMA{}
func (inc *RMA) PushK(k types.KLine) {
inc.Update(k.Close.Float64())
}
func (inc *RMA) calculateAndUpdate(kLines []types.KLine) { func (inc *RMA) calculateAndUpdate(kLines []types.KLine) {
for _, k := range kLines { for _, k := range kLines {
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
continue continue
} }
inc.Update(k.Close.Float64())
inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())

View File

@ -80,12 +80,17 @@ func (inc *RSI) Length() int {
var _ types.SeriesExtend = &RSI{} 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 { for _, k := range kLines {
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
continue continue
} }
inc.Update(k.Close.Float64())
inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
@ -97,7 +102,7 @@ func (inc *RSI) handleKLineWindowUpdate(interval types.Interval, window types.KL
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *RSI) Bind(updater KLineWindowUpdater) { func (inc *RSI) Bind(updater KLineWindowUpdater) {

View File

@ -59,7 +59,7 @@ func Test_calculateRSI(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
rsi := RSI{IntervalWindow: types.IntervalWindow{Window: tt.window}} 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)) assert.Equal(t, len(rsi.Values), len(tt.want))
for i, v := range rsi.Values { for i, v := range rsi.Values {
assert.InDelta(t, v, tt.want[i], Delta) assert.InDelta(t, v, tt.want[i], Delta)

View File

@ -10,8 +10,6 @@ import (
const MaxNumOfSMA = 5_000 const MaxNumOfSMA = 5_000
const MaxNumOfSMATruncateSize = 100 const MaxNumOfSMATruncateSize = 100
var zeroTime time.Time
//go:generate callbackgen -type SMA //go:generate callbackgen -type SMA
type SMA struct { type SMA struct {
types.SeriesBase types.SeriesBase
@ -59,20 +57,25 @@ func (inc *SMA) Update(value float64) {
} }
} }
func (inc *SMA) calculateAndUpdate(kLines []types.KLine) { func (inc *SMA) PushK(k types.KLine) {
inc.Update(k.Close.Float64())
}
func (inc *SMA) CalculateAndUpdate(kLines []types.KLine) {
var index = len(kLines) - 1 var index = len(kLines) - 1
var kline = kLines[index] var kline = kLines[index]
if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) { if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) {
return return
} }
if inc.Cache == nil { if inc.Cache == nil {
for _, k := range kLines { for _, k := range kLines {
inc.Update(KLineClosePriceMapper(k)) inc.PushK(k)
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Values.Last()) inc.EmitUpdate(inc.Values.Last())
} }
} else { } else {
inc.Update(KLineClosePriceMapper(kline)) inc.PushK(kline)
inc.EndTime = kline.EndTime.Time() inc.EndTime = kline.EndTime.Time()
inc.EmitUpdate(inc.Values.Last()) inc.EmitUpdate(inc.Values.Last())
} }
@ -83,7 +86,7 @@ func (inc *SMA) handleKLineWindowUpdate(interval types.Interval, window types.KL
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *SMA) Bind(updater KLineWindowUpdater) { func (inc *SMA) Bind(updater KLineWindowUpdater) {

View File

@ -52,7 +52,7 @@ func Test_SMA(t *testing.T) {
sma := SMA{ sma := SMA{
IntervalWindow: types.IntervalWindow{Window: 5}, 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.want, sma.Last(), Delta)
assert.InDelta(t, tt.next, sma.Index(1), Delta) assert.InDelta(t, tt.next, sma.Index(1), Delta)
sma.Update(tt.update) sma.Update(tt.update)

View File

@ -93,14 +93,19 @@ func (inc *SSF) Last() float64 {
var _ types.SeriesExtend = &SSF{} 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 { if inc.Values != nil {
inc.Update(allKLines[len(allKLines)-1].Close.Float64()) k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
return return
} }
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
@ -109,7 +114,7 @@ func (inc *SSF) handleKLineWindowUpdate(interval types.Interval, window types.KL
if inc.Interval != interval { if inc.Interval != interval {
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *SSF) Bind(updater KLineWindowUpdater) { func (inc *SSF) Bind(updater KLineWindowUpdater) {

View File

@ -62,7 +62,7 @@ func Test_SSF(t *testing.T) {
IntervalWindow: types.IntervalWindow{Window: 5}, IntervalWindow: types.IntervalWindow{Window: 5},
Poles: tt.poles, Poles: tt.poles,
} }
ssf.calculateAndUpdate(tt.kLines) ssf.CalculateAndUpdate(tt.kLines)
assert.InDelta(t, tt.want, ssf.Last(), Delta) assert.InDelta(t, tt.want, ssf.Last(), Delta)
assert.InDelta(t, tt.next, ssf.Index(1), Delta) assert.InDelta(t, tt.next, ssf.Index(1), Delta)
assert.Equal(t, tt.all, ssf.Length()) assert.Equal(t, tt.all, ssf.Length())

View File

@ -59,7 +59,11 @@ func (inc *STOCH) LastD() float64 {
return inc.D[len(inc.D)-1] 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 { if len(kLines) < inc.Window || len(kLines) < DPeriod {
return return
} }
@ -68,7 +72,8 @@ func (inc *STOCH) calculateAndUpdate(kLines []types.KLine) {
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
continue continue
} }
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
inc.PushK(k)
} }
inc.EmitUpdate(inc.LastK(), inc.LastD()) inc.EmitUpdate(inc.LastK(), inc.LastD())
@ -80,7 +85,7 @@ func (inc *STOCH) handleKLineWindowUpdate(interval types.Interval, window types.
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *STOCH) Bind(updater KLineWindowUpdater) { func (inc *STOCH) Bind(updater KLineWindowUpdater) {

View File

@ -56,7 +56,7 @@ func TestSTOCH_update(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
kd := STOCH{IntervalWindow: types.IntervalWindow{Window: tt.window}} kd := STOCH{IntervalWindow: types.IntervalWindow{Window: tt.window}}
kd.calculateAndUpdate(tt.kLines) kd.CalculateAndUpdate(tt.kLines)
got_k := kd.LastK() got_k := kd.LastK()
diff_k := math.Trunc((got_k-tt.want_k)*100) / 100 diff_k := math.Trunc((got_k-tt.want_k)*100) / 100

View File

@ -55,14 +55,19 @@ func (inc *TEMA) Length() int {
var _ types.SeriesExtend = &TEMA{} 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 { if inc.A1 == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} else { } else {
inc.Update(allKLines[len(allKLines)-1].Close.Float64()) k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
@ -72,7 +77,7 @@ func (inc *TEMA) handleKLineWindowUpdate(interval types.Interval, window types.K
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *TEMA) Bind(updater KLineWindowUpdater) { func (inc *TEMA) Bind(updater KLineWindowUpdater) {

View File

@ -46,7 +46,7 @@ func Test_TEMA(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tema := TEMA{IntervalWindow: types.IntervalWindow{Window: 16}} tema := TEMA{IntervalWindow: types.IntervalWindow{Window: 16}}
tema.calculateAndUpdate(tt.kLines) tema.CalculateAndUpdate(tt.kLines)
last := tema.Last() last := tema.Last()
assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.want, last, Delta)
assert.InDelta(t, tt.next, tema.Index(1), Delta) assert.InDelta(t, tt.next, tema.Index(1), Delta)

View File

@ -23,7 +23,8 @@ type TILL struct {
c2 float64 c2 float64
c3 float64 c3 float64
c4 float64 c4 float64
UpdateCallbacks []func(value float64)
updateCallbacks []func(value float64)
} }
func (inc *TILL) Update(value float64) { func (inc *TILL) Update(value float64) {
@ -85,7 +86,11 @@ func (inc *TILL) Length() int {
var _ types.Series = &TILL{} 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 doable := false
if inc.e1 == nil { if inc.e1 == nil {
doable = true doable = true
@ -94,8 +99,9 @@ func (inc *TILL) calculateAndUpdate(allKLines []types.KLine) {
if !doable && k.StartTime.After(inc.e1.LastOpenTime) { if !doable && k.StartTime.After(inc.e1.LastOpenTime) {
doable = true doable = true
} }
if doable { if doable {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
@ -106,7 +112,7 @@ func (inc *TILL) handleKLineWindowUpdate(interval types.Interval, window types.K
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *TILL) Bind(updater KLineWindowUpdater) { func (inc *TILL) Bind(updater KLineWindowUpdater) {

View File

@ -5,11 +5,11 @@ package indicator
import () import ()
func (inc *TILL) OnUpdate(cb func(value float64)) { 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) { func (inc *TILL) EmitUpdate(value float64) {
for _, cb := range inc.UpdateCallbacks { for _, cb := range inc.updateCallbacks {
cb(value) cb(value)
} }
} }

View File

@ -4,9 +4,10 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
till := TILL{IntervalWindow: types.IntervalWindow{Window: 16}} till := TILL{IntervalWindow: types.IntervalWindow{Window: 16}}
till.calculateAndUpdate(tt.kLines) till.CalculateAndUpdate(tt.kLines)
last := till.Last() last := till.Last()
assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.want, last, Delta)
assert.InDelta(t, tt.next, till.Index(1), Delta) assert.InDelta(t, tt.next, till.Index(1), Delta)

View File

@ -50,14 +50,19 @@ func (inc *TMA) Length() int {
var _ types.SeriesExtend = &TMA{} 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 { if inc.s1 == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} else { } else {
inc.Update(allKLines[len(allKLines)-1].Close.Float64()) k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
@ -67,7 +72,7 @@ func (inc *TMA) handleKLineWindowUpdate(interval types.Interval, window types.KL
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *TMA) Bind(updater KLineWindowUpdater) { func (inc *TMA) Bind(updater KLineWindowUpdater) {

View File

@ -1,26 +1,2 @@
package indicator 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
}

View File

@ -15,7 +15,7 @@ type VIDYA struct {
Values types.Float64Slice Values types.Float64Slice
input types.Float64Slice input types.Float64Slice
UpdateCallbacks []func(value float64) updateCallbacks []func(value float64)
} }
func (inc *VIDYA) Update(value float64) { func (inc *VIDYA) Update(value float64) {
@ -70,14 +70,19 @@ func (inc *VIDYA) Length() int {
var _ types.SeriesExtend = &VIDYA{} var _ types.SeriesExtend = &VIDYA{}
func (inc *VIDYA) PushK(k types.KLine) {
inc.Update(k.Close.Float64())
}
func (inc *VIDYA) calculateAndUpdate(allKLines []types.KLine) { func (inc *VIDYA) calculateAndUpdate(allKLines []types.KLine) {
if inc.input.Length() == 0 { if inc.input.Length() == 0 {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} else { } else {
inc.Update(allKLines[len(allKLines)-1].Close.Float64()) k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }

View File

@ -5,11 +5,11 @@ package indicator
import () import ()
func (inc *VIDYA) OnUpdate(cb func(value float64)) { 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) { func (inc *VIDYA) EmitUpdate(value float64) {
for _, cb := range inc.UpdateCallbacks { for _, cb := range inc.updateCallbacks {
cb(value) cb(value)
} }
} }

View File

@ -13,10 +13,10 @@ import (
const MaxNumOfVOL = 5_000 const MaxNumOfVOL = 5_000
const MaxNumOfVOLTruncateSize = 100 const MaxNumOfVOLTruncateSize = 100
//var zeroTime time.Time // var zeroTime time.Time
//go:generate callbackgen -type VOLATILITY //go:generate callbackgen -type Volatility
type VOLATILITY struct { type Volatility struct {
types.SeriesBase types.SeriesBase
types.IntervalWindow types.IntervalWindow
Values types.Float64Slice Values types.Float64Slice
@ -25,42 +25,43 @@ type VOLATILITY struct {
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
func (inc *VOLATILITY) Last() float64 { func (inc *Volatility) Last() float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0.0 return 0.0
} }
return inc.Values[len(inc.Values)-1] 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 { if len(inc.Values)-i <= 0 {
return 0.0 return 0.0
} }
return inc.Values[len(inc.Values)-i-1] return inc.Values[len(inc.Values)-i-1]
} }
func (inc *VOLATILITY) Length() int { func (inc *Volatility) Length() int {
return len(inc.Values) return len(inc.Values)
} }
var _ types.SeriesExtend = &VOLATILITY{} var _ types.SeriesExtend = &Volatility{}
func (inc *VOLATILITY) calculateAndUpdate(klines []types.KLine) { func (inc *Volatility) CalculateAndUpdate(allKLines []types.KLine) {
if len(klines) < inc.Window { if len(allKLines) < inc.Window {
return return
} }
var end = len(klines) - 1 var end = len(allKLines) - 1
var lastKLine = klines[end] var lastKLine = allKLines[end]
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return return
} }
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc 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) volatility, err := calculateVOLATILITY(recentT, inc.Window, KLineClosePriceMapper)
if err != nil { if err != nil {
@ -73,20 +74,20 @@ func (inc *VOLATILITY) calculateAndUpdate(klines []types.KLine) {
inc.Values = inc.Values[MaxNumOfVOLTruncateSize-1:] inc.Values = inc.Values[MaxNumOfVOLTruncateSize-1:]
} }
inc.EndTime = klines[end].GetEndTime().Time() inc.EndTime = allKLines[end].GetEndTime().Time()
inc.EmitUpdate(volatility) 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 { if inc.Interval != interval {
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *VOLATILITY) Bind(updater KLineWindowUpdater) { func (inc *Volatility) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
} }

View File

@ -1,14 +1,14 @@
// Code generated by "callbackgen -type VOLATILITY"; DO NOT EDIT. // Code generated by "callbackgen -type Volatility"; DO NOT EDIT.
package indicator package indicator
import () import ()
func (inc *VOLATILITY) OnUpdate(cb func(value float64)) { func (inc *Volatility) OnUpdate(cb func(value float64)) {
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
} }
func (inc *VOLATILITY) EmitUpdate(value float64) { func (inc *Volatility) EmitUpdate(value float64) {
for _, cb := range inc.UpdateCallbacks { for _, cb := range inc.UpdateCallbacks {
cb(value) cb(value)
} }

View File

@ -71,18 +71,21 @@ func (inc *VWAP) Length() int {
var _ types.SeriesExtend = &VWAP{} var _ types.SeriesExtend = &VWAP{}
func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) { func (inc *VWAP) PushK(k types.KLine) {
var priceF = KLineTypicalPriceMapper 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) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
continue continue
} }
inc.Update(priceF(k), k.Volume.Float64())
inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) 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) { 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 return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *VWAP) Bind(updater KLineWindowUpdater) { func (inc *VWAP) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) 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}} vwap := VWAP{IntervalWindow: types.IntervalWindow{Window: window}}
for _, k := range klines { for _, k := range klines {
vwap.Update(priceF(k), k.Volume.Float64()) vwap.Update(priceF(k), k.Volume.Float64())

View File

@ -4,12 +4,12 @@ package indicator
import () import ()
func (V *VWAP) OnUpdate(cb func(value float64)) { func (inc *VWAP) OnUpdate(cb func(value float64)) {
V.UpdateCallbacks = append(V.UpdateCallbacks, cb) inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
} }
func (V *VWAP) EmitUpdate(value float64) { func (inc *VWAP) EmitUpdate(value float64) {
for _, cb := range V.UpdateCallbacks { for _, cb := range inc.UpdateCallbacks {
cb(value) cb(value)
} }
} }

View File

@ -64,7 +64,7 @@ func Test_calculateVWAP(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
priceF := KLineTypicalPriceMapper priceF := KLineTypicalPriceMapper
got := CalculateVWAP(tt.kLines, priceF, tt.window) got := calculateVWAP(tt.kLines, priceF, tt.window)
diff := math.Trunc((got-tt.want)*100) / 100 diff := math.Trunc((got-tt.want)*100) / 100
if diff != 0 { if diff != 0 {
t.Errorf("calculateVWAP() = %v, want %v", got, tt.want) t.Errorf("calculateVWAP() = %v, want %v", got, tt.want)

View File

@ -49,27 +49,20 @@ func (inc *VWMA) Length() int {
var _ types.SeriesExtend = &VWMA{} var _ types.SeriesExtend = &VWMA{}
func KLinePriceVolumeMapper(k types.KLine) float64 {
return k.Close.Mul(k.Volume).Float64()
}
func KLineVolumeMapper(k types.KLine) float64 { func (inc *VWMA) CalculateAndUpdate(allKLines []types.KLine) {
return k.Volume.Float64() if len(allKLines) < inc.Window {
}
func (inc *VWMA) calculateAndUpdate(kLines []types.KLine) {
if len(kLines) < inc.Window {
return return
} }
var index = len(kLines) - 1 var index = len(allKLines) - 1
var kline = kLines[index] var kline = allKLines[index]
if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) { if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) {
return 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) pv, err := calculateSMA(recentK, inc.Window, KLinePriceVolumeMapper)
if err != nil { if err != nil {
@ -93,7 +86,7 @@ func (inc *VWMA) calculateAndUpdate(kLines []types.KLine) {
inc.Values = inc.Values[MaxNumOfSMATruncateSize-1:] inc.Values = inc.Values[MaxNumOfSMATruncateSize-1:]
} }
inc.EndTime = kLines[index].EndTime.Time() inc.EndTime = allKLines[index].EndTime.Time()
inc.EmitUpdate(vwma) inc.EmitUpdate(vwma)
} }
@ -103,7 +96,7 @@ func (inc *VWMA) handleKLineWindowUpdate(interval types.Interval, window types.K
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *VWMA) Bind(updater KLineWindowUpdater) { func (inc *VWMA) Bind(updater KLineWindowUpdater) {

View File

@ -1,8 +1,9 @@
package indicator package indicator
import ( import (
"github.com/c9s/bbgo/pkg/types"
"time" "time"
"github.com/c9s/bbgo/pkg/types"
) )
// Refer: Welles Wilder's Moving Average // Refer: Welles Wilder's Moving Average
@ -56,7 +57,11 @@ func (inc *WWMA) Length() int {
return len(inc.Values) 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 { if len(allKLines) < inc.Window {
// we can't calculate // we can't calculate
return return
@ -68,7 +73,7 @@ func (inc *WWMA) calculateAndUpdate(allKLines []types.KLine) {
doable = true doable = true
} }
if doable { if doable {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.LastOpenTime = k.StartTime.Time() inc.LastOpenTime = k.StartTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
@ -80,7 +85,7 @@ func (inc *WWMA) handleKLineWindowUpdate(interval types.Interval, window types.K
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *WWMA) Bind(updater KLineWindowUpdater) { func (inc *WWMA) Bind(updater KLineWindowUpdater) {

View File

@ -16,7 +16,7 @@ type ZLEMA struct {
zlema *EWMA zlema *EWMA
lag int lag int
UpdateCallbacks []func(value float64) updateCallbacks []func(value float64)
} }
func (inc *ZLEMA) Index(i int) float64 { func (inc *ZLEMA) Index(i int) float64 {
@ -59,14 +59,19 @@ func (inc *ZLEMA) Update(value float64) {
var _ types.SeriesExtend = &ZLEMA{} 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 { if inc.zlema == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} else { } else {
inc.Update(allKLines[len(allKLines)-1].Close.Float64()) k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last())
} }
} }
@ -76,7 +81,7 @@ func (inc *ZLEMA) handleKLineWindowUpdate(interval types.Interval, window types.
return return
} }
inc.calculateAndUpdate(window) inc.CalculateAndUpdate(window)
} }
func (inc *ZLEMA) Bind(updater KLineWindowUpdater) { func (inc *ZLEMA) Bind(updater KLineWindowUpdater) {

View File

@ -5,11 +5,11 @@ package indicator
import () import ()
func (inc *ZLEMA) OnUpdate(cb func(value float64)) { 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) { func (inc *ZLEMA) EmitUpdate(value float64) {
for _, cb := range inc.UpdateCallbacks { for _, cb := range inc.updateCallbacks {
cb(value) cb(value)
} }
} }

View File

@ -45,7 +45,7 @@ func Test_ZLEMA(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
zlema := ZLEMA{IntervalWindow: types.IntervalWindow{Window: 16}} zlema := ZLEMA{IntervalWindow: types.IntervalWindow{Window: 16}}
zlema.calculateAndUpdate(tt.kLines) zlema.CalculateAndUpdate(tt.kLines)
last := zlema.Last() last := zlema.Last()
assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.want, last, Delta)
assert.InDelta(t, tt.next, zlema.Index(1), Delta) assert.InDelta(t, tt.next, zlema.Index(1), Delta)

View File

@ -296,7 +296,7 @@ func preloadPivot(pivot *indicator.Pivot, store *bbgo.MarketDataStore) *types.KL
log.Debugf("updating pivot indicator: %d klines", len(*klines)) log.Debugf("updating pivot indicator: %d klines", len(*klines))
for i := pivot.Window; i < len(*klines); i++ { 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) log.Debugf("found %v previous lows: %v", pivot.IntervalWindow, pivot.Lows)