diff --git a/pkg/indicator/ewma.go b/pkg/indicator/ewma.go index 11c071967f..c099415343 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) } +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 - return - } - - var priceF = KLineClosePriceMapper - var dataLen = len(allKLines) - var multiplier = 2.0 / (float64(inc.Window) + 1) +func (inc *EWMA) PushK(k types.KLine) { + inc.Update(k.Close.Float64()) + inc.LastOpenTime = k.StartTime.Time() +} - // init the values fromNthK the kline data - var fromNthK = 1 +func (inc *EWMA) CalculateAndUpdate(allKLines []types.KLine) { 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:] + for _, k := range allKLines { + inc.PushK(k) } - 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 - } - } + inc.EmitUpdate(inc.Last()) + } else { + k := allKLines[len(allKLines)-1] + inc.PushK(k) + inc.EmitUpdate(inc.Last()) } +} - 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) +func (inc *EWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return } - 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) - } + inc.CalculateAndUpdate(window) +} - 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{}