2020-10-29 09:51:20 +00:00
|
|
|
package indicator
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
2020-12-05 05:04:32 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2020-10-29 09:51:20 +00:00
|
|
|
"gonum.org/v1/gonum/stat"
|
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
2020-11-30 05:06:35 +00:00
|
|
|
boll implements the bollinger indicator:
|
2020-10-29 09:51:20 +00:00
|
|
|
|
|
|
|
The Basics of Bollinger Bands
|
|
|
|
- https://www.investopedia.com/articles/technical/102201.asp
|
|
|
|
|
|
|
|
Bollinger Bands
|
|
|
|
- https://www.investopedia.com/terms/b/bollingerbands.asp
|
|
|
|
|
|
|
|
Bollinger Bands Technical indicator guide:
|
|
|
|
- https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/bollinger-bands
|
|
|
|
*/
|
2020-10-29 11:09:45 +00:00
|
|
|
|
|
|
|
//go:generate callbackgen -type BOLL
|
2020-10-29 09:51:20 +00:00
|
|
|
type BOLL struct {
|
|
|
|
types.IntervalWindow
|
|
|
|
|
|
|
|
// times of Std, generally it's 2
|
|
|
|
K float64
|
|
|
|
|
2021-05-22 12:20:48 +00:00
|
|
|
SMA types.Float64Slice
|
|
|
|
StdDev types.Float64Slice
|
|
|
|
UpBand types.Float64Slice
|
|
|
|
DownBand types.Float64Slice
|
2020-10-29 09:51:20 +00:00
|
|
|
|
|
|
|
EndTime time.Time
|
2020-10-29 11:09:45 +00:00
|
|
|
|
|
|
|
updateCallbacks []func(sma, upBand, downBand float64)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (inc *BOLL) LastUpBand() float64 {
|
2020-10-31 12:36:58 +00:00
|
|
|
if len(inc.UpBand) == 0 {
|
|
|
|
return 0.0
|
|
|
|
}
|
|
|
|
|
2020-10-29 11:09:45 +00:00
|
|
|
return inc.UpBand[len(inc.UpBand)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (inc *BOLL) LastDownBand() float64 {
|
2020-10-31 12:36:58 +00:00
|
|
|
if len(inc.DownBand) == 0 {
|
|
|
|
return 0.0
|
|
|
|
}
|
|
|
|
|
2020-10-29 11:09:45 +00:00
|
|
|
return inc.DownBand[len(inc.DownBand)-1]
|
2020-10-29 09:51:20 +00:00
|
|
|
}
|
|
|
|
|
2020-11-10 08:55:35 +00:00
|
|
|
func (inc *BOLL) LastStdDev() float64 {
|
|
|
|
if len(inc.StdDev) == 0 {
|
|
|
|
return 0.0
|
|
|
|
}
|
|
|
|
|
|
|
|
return inc.StdDev[len(inc.StdDev)-1]
|
|
|
|
}
|
|
|
|
|
2020-10-29 09:51:20 +00:00
|
|
|
func (inc *BOLL) LastSMA() float64 {
|
2022-01-07 18:18:44 +00:00
|
|
|
if len(inc.SMA) > 0 {
|
|
|
|
return inc.SMA[len(inc.SMA)-1]
|
|
|
|
}
|
|
|
|
return 0.0
|
2020-10-29 09:51:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (inc *BOLL) calculateAndUpdate(kLines []types.KLine) {
|
|
|
|
if len(kLines) < inc.Window {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var index = len(kLines) - 1
|
|
|
|
var kline = kLines[index]
|
|
|
|
|
|
|
|
if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var recentK = kLines[index-(inc.Window-1) : index+1]
|
2020-12-05 05:32:16 +00:00
|
|
|
sma, err := calculateSMA(recentK, inc.Window, KLineClosePriceMapper)
|
2020-12-05 05:04:32 +00:00
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("SMA error")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-29 09:51:20 +00:00
|
|
|
inc.SMA.Push(sma)
|
|
|
|
|
|
|
|
var prices []float64
|
|
|
|
for _, k := range recentK {
|
2022-02-03 04:55:25 +00:00
|
|
|
prices = append(prices, k.Close.Float64())
|
2020-10-29 09:51:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var std = stat.StdDev(prices, nil)
|
|
|
|
inc.StdDev.Push(std)
|
|
|
|
|
2020-10-29 11:47:53 +00:00
|
|
|
var band = inc.K * std
|
|
|
|
|
|
|
|
var upBand = sma + band
|
2020-10-29 09:51:20 +00:00
|
|
|
inc.UpBand.Push(upBand)
|
|
|
|
|
2020-10-29 11:47:53 +00:00
|
|
|
var downBand = sma - band
|
2020-10-29 09:51:20 +00:00
|
|
|
inc.DownBand.Push(downBand)
|
|
|
|
|
|
|
|
// update end time
|
2021-12-15 05:04:01 +00:00
|
|
|
inc.EndTime = kLines[index].EndTime.Time()
|
2020-10-29 11:47:53 +00:00
|
|
|
|
2020-10-31 12:36:58 +00:00
|
|
|
// log.Infof("update boll: sma=%f, up=%f, down=%f", sma, upBand, downBand)
|
|
|
|
|
2020-10-29 11:47:53 +00:00
|
|
|
inc.EmitUpdate(sma, upBand, downBand)
|
2020-10-29 09:51:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (inc *BOLL) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
|
|
|
|
if inc.Interval != interval {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if inc.EndTime != zeroTime && inc.EndTime.Before(inc.EndTime) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
inc.calculateAndUpdate(window)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (inc *BOLL) Bind(updater KLineWindowUpdater) {
|
|
|
|
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
|
|
|
}
|