Skip to content

Commit

Permalink
improve and fix ewma calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
c9s committed Dec 5, 2020
1 parent b188901 commit 900f822
Show file tree
Hide file tree
Showing 5 changed files with 1,145 additions and 33 deletions.
5 changes: 0 additions & 5 deletions pkg/bbgo/marketdatastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ type MarketDataStore struct {
// KLineWindows stores all loaded klines per interval
KLineWindows map[types.Interval]types.KLineWindow `json:"-"`

LastKLine types.KLine

kLineWindowUpdateCallbacks []func(interval types.Interval, kline types.KLineWindow)

orderBook *types.StreamOrderBook
Expand Down Expand Up @@ -86,8 +84,5 @@ func (store *MarketDataStore) AddKLine(kline types.KLine) {
window.Add(kline)
}
store.KLineWindows[kline.Interval] = window

store.LastKLine = kline

store.EmitKLineWindowUpdate(kline.Interval, window)
}
8 changes: 7 additions & 1 deletion pkg/indicator/boll.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package indicator
import (
"time"

log "github.com/sirupsen/logrus"
"gonum.org/v1/gonum/stat"

"github.com/c9s/bbgo/pkg/types"
Expand Down Expand Up @@ -79,7 +80,12 @@ func (inc *BOLL) calculateAndUpdate(kLines []types.KLine) {
}

var recentK = kLines[index-(inc.Window-1) : index+1]
var sma = calculateSMA(recentK)
sma, err := calculateSMA(recentK, inc.Window)
if err != nil {
log.WithError(err).Error("SMA error")
return
}

inc.SMA.Push(sma)

var prices []float64
Expand Down
88 changes: 69 additions & 19 deletions pkg/indicator/ewma.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package indicator

import (
"math"
"time"

log "github.com/sirupsen/logrus"

"github.com/c9s/bbgo/pkg/types"
)

//go:generate callbackgen -type EWMA
type EWMA struct {
types.IntervalWindow
Values Float64Slice
EndTime time.Time
Values Float64Slice
LastOpenTime time.Time

UpdateCallbacks []func(value float64)
}
Expand All @@ -19,35 +22,82 @@ func (inc *EWMA) Last() float64 {
return inc.Values[len(inc.Values)-1]
}

func (inc *EWMA) calculateAndUpdate(kLines []types.KLine) {
if len(kLines) < inc.Window {
func (inc *EWMA) calculateAndUpdate(allKLines []types.KLine) {
if len(allKLines) < inc.Window {
// we can't calculate
return
}

var index = len(kLines) - 1
var lastK = kLines[index]
if inc.EndTime != zeroTime && lastK.EndTime.Before(inc.EndTime) {
return
var priceF = KLineClosePriceMapper
var dataLen = len(allKLines)
var multiplier = 2.0 / (float64(inc.Window) + 1)

// init the values from the kline data
var from = 1
if len(inc.Values) == 0 {
// for the first value, we should use the close price
inc.Values = []float64{priceF(allKLines[0])}
} else {
// update ewma with the existing values
for i := dataLen - 1; i > 0; i-- {
if allKLines[i].StartTime.After(inc.LastOpenTime) {
from = i
} else {
break
}
}
}

for i := from; 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
inc.EmitUpdate(ewma)
}

var recentK = kLines[index-(inc.Window-1) : index+1]
var multiplier = 2.0 / float64(inc.Window+1)
var val = calculateEWMA(recentK, multiplier)
// val = calculateSMA(recentK)
inc.Values.Push(val)
inc.EndTime = lastK.EndTime
inc.EmitUpdate(val)
if len(inc.Values) != dataLen {
log.Warnf("EMA %s (%d) value length (%d) != all kline data 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(CalculateKLineEWMA(allKLines, priceF, inc.Window)*100.0) / 100.0
if v1 != v2 {
log.Warnf("ACCUMULATED EMA %s (%d) %f != EMA %f", inc.Interval, inc.Window, v1, v2)
}
}

func CalculateKLineEWMA(allKLines []types.KLine, priceF KLinePriceMapper, window int) float64 {
var multiplier = 2.0 / (float64(window) + 1)
return ewma(MapKLinePrice(allKLines, priceF), multiplier)
}

// see https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp
func calculateEWMA(kLines []types.KLine, multiplier float64) float64 {
var end = len(kLines) - 1
func ewma(prices []float64, multiplier float64) float64 {
var end = len(prices) - 1
if end == 0 {
return kLines[0].Close
return prices[0]
}

return prices[end]*multiplier + (1-multiplier)*ewma(prices[:end], multiplier)
}

type KLinePriceMapper func(k types.KLine) float64

func KLineOpenPriceMapper(k types.KLine) float64 {
return k.Open
}

func KLineClosePriceMapper(k types.KLine) float64 {
return k.Close
}

func MapKLinePrice(kLines []types.KLine, f KLinePriceMapper) (prices []float64) {
for _, k := range kLines {
prices = append(prices, f(k))
}

return kLines[end].Close*multiplier + (1-multiplier)*calculateEWMA(kLines[:end-1], multiplier)
return prices
}

type KLineWindowUpdater interface {
Expand Down
Loading

0 comments on commit 900f822

Please sign in to comment.