indicator: rewrite boll indicator with stddev indicator

This commit is contained in:
c9s 2022-07-14 12:51:27 +08:00
parent 975d0d6995
commit a5715c6aee
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
5 changed files with 141 additions and 57 deletions

View File

@ -3,9 +3,6 @@ package indicator
import ( import (
"time" "time"
log "github.com/sirupsen/logrus"
"gonum.org/v1/gonum/stat"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
@ -24,13 +21,15 @@ Bollinger Bands Technical indicator guide:
//go:generate callbackgen -type BOLL //go:generate callbackgen -type BOLL
type BOLL struct { type BOLL struct {
types.SeriesBase
types.IntervalWindow types.IntervalWindow
// times of Std, generally it's 2 // K is the multiplier of Std, generally it's 2
K float64 K float64
SMA types.Float64Slice SMA *SMA
StdDev types.Float64Slice StdDev *StdDev
UpBand types.Float64Slice UpBand types.Float64Slice
DownBand types.Float64Slice DownBand types.Float64Slice
@ -50,11 +49,11 @@ func (inc *BOLL) GetDownBand() types.SeriesExtend {
} }
func (inc *BOLL) GetSMA() types.SeriesExtend { func (inc *BOLL) GetSMA() types.SeriesExtend {
return types.NewSeries(&inc.SMA) return types.NewSeries(inc.SMA)
} }
func (inc *BOLL) GetStdDev() types.SeriesExtend { func (inc *BOLL) GetStdDev() types.SeriesExtend {
return types.NewSeries(&inc.StdDev) return types.NewSeries(inc.StdDev)
} }
func (inc *BOLL) LastUpBand() float64 { func (inc *BOLL) LastUpBand() float64 {
@ -73,64 +72,49 @@ func (inc *BOLL) LastDownBand() float64 {
return inc.DownBand[len(inc.DownBand)-1] return inc.DownBand[len(inc.DownBand)-1]
} }
func (inc *BOLL) LastStdDev() float64 { func (inc *BOLL) Update(value float64) {
if len(inc.StdDev) == 0 { if inc.SMA == nil {
return 0.0 inc.SeriesBase.Series = inc
inc.SMA = &SMA{IntervalWindow: inc.IntervalWindow}
} }
return inc.StdDev[len(inc.StdDev)-1] if inc.StdDev == nil {
} inc.StdDev = &StdDev{IntervalWindow: inc.IntervalWindow}
func (inc *BOLL) LastSMA() float64 {
if len(inc.SMA) > 0 {
return inc.SMA[len(inc.SMA)-1]
}
return 0.0
}
func (inc *BOLL) CalculateAndUpdate(kLines []types.KLine) {
if len(kLines) < inc.Window {
return
} }
var index = len(kLines) - 1 inc.SMA.Update(value)
var kline = kLines[index] inc.StdDev.Update(value)
if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) { var sma = inc.SMA.Last()
return var stdDev = inc.StdDev.Last()
} var band = inc.K * stdDev
var recentK = kLines[index-(inc.Window-1) : index+1]
sma, err := calculateSMA(recentK, inc.Window, KLineClosePriceMapper)
if err != nil {
log.WithError(err).Error("SMA error")
return
}
inc.SMA.Push(sma)
var prices []float64
for _, k := range recentK {
prices = append(prices, k.Close.Float64())
}
var std = stat.StdDev(prices, nil)
inc.StdDev.Push(std)
var band = inc.K * std
var upBand = sma + band var upBand = sma + band
inc.UpBand.Push(upBand)
var downBand = sma - band var downBand = sma - band
inc.UpBand.Push(upBand)
inc.DownBand.Push(downBand) inc.DownBand.Push(downBand)
}
// update end time func (inc *BOLL) PushK(k types.KLine) {
inc.EndTime = kLines[index].EndTime.Time() inc.Update(k.Close.Float64())
}
// log.Infof("update boll: sma=%f, up=%f, down=%f", sma, upBand, downBand) func (inc *BOLL) CalculateAndUpdate(allKLines []types.KLine) {
var last = allKLines[len(allKLines)-1]
inc.EmitUpdate(sma, upBand, downBand) if inc.SMA == nil {
for _, k := range allKLines {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
continue
}
inc.PushK(k)
}
} else {
inc.PushK(last)
}
inc.EmitUpdate(inc.SMA.Last(), inc.UpBand.Last(), inc.DownBand.Last())
} }
func (inc *BOLL) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { func (inc *BOLL) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {

85
pkg/indicator/stddev.go Normal file
View File

@ -0,0 +1,85 @@
package indicator
import (
"time"
"github.com/c9s/bbgo/pkg/types"
)
//go:generate callbackgen -type StdDev
type StdDev struct {
types.SeriesBase
types.IntervalWindow
Values types.Float64Slice
rawValues *types.Queue
EndTime time.Time
updateCallbacks []func(value float64)
}
func (inc *StdDev) Last() float64 {
if inc.Values.Length() == 0 {
return 0.0
}
return inc.Values.Last()
}
func (inc *StdDev) Index(i int) float64 {
if i >= inc.Values.Length() {
return 0.0
}
return inc.Values.Index(i)
}
func (inc *StdDev) Length() int {
return inc.Values.Length()
}
var _ types.SeriesExtend = &StdDev{}
func (inc *StdDev) Update(value float64) {
if inc.rawValues == nil {
inc.rawValues = types.NewQueue(inc.Window)
inc.SeriesBase.Series = inc
}
inc.rawValues.Update(value)
var std = inc.rawValues.Stdev()
inc.Values.Push(std)
}
func (inc *StdDev) PushK(k types.KLine) {
inc.Update(k.Close.Float64())
inc.EndTime = k.EndTime.Time()
}
func (inc *StdDev) 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.PushK(last)
}
inc.EmitUpdate(inc.Values.Last())
}
func (inc *StdDev) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.CalculateAndUpdate(window)
}
func (inc *StdDev) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}

View File

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

View File

@ -280,7 +280,7 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
downBand := s.defaultBoll.LastDownBand() downBand := s.defaultBoll.LastDownBand()
upBand := s.defaultBoll.LastUpBand() upBand := s.defaultBoll.LastUpBand()
sma := s.defaultBoll.LastSMA() sma := s.defaultBoll.SMA.Last()
log.Infof("%s bollinger band: up %f sma %f down %f", s.Symbol, upBand, sma, downBand) log.Infof("%s bollinger band: up %f sma %f down %f", s.Symbol, upBand, sma, downBand)
bandPercentage := calculateBandPercentage(upBand, downBand, sma, midPrice.Float64()) bandPercentage := calculateBandPercentage(upBand, downBand, sma, midPrice.Float64())
@ -402,7 +402,7 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
canSell = false canSell = false
} }
if s.BuyBelowNeutralSMA && midPrice.Float64() > s.neutralBoll.LastSMA() { if s.BuyBelowNeutralSMA && midPrice.Float64() > s.neutralBoll.SMA.Last() {
canBuy = false canBuy = false
} }

View File

@ -338,7 +338,7 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
downBand := s.defaultBoll.LastDownBand() downBand := s.defaultBoll.LastDownBand()
upBand := s.defaultBoll.LastUpBand() upBand := s.defaultBoll.LastUpBand()
sma := s.defaultBoll.LastSMA() sma := s.defaultBoll.SMA.Last()
log.Infof("bollinger band: up %f sma %f down %f", upBand, sma, downBand) log.Infof("bollinger band: up %f sma %f down %f", upBand, sma, downBand)
bandPercentage := calculateBandPercentage(upBand, downBand, sma, midPrice.Float64()) bandPercentage := calculateBandPercentage(upBand, downBand, sma, midPrice.Float64())