From 7de997533653ce766fe2db768a8b7220a4931c05 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Fri, 21 Oct 2022 16:14:47 +0800 Subject: [PATCH] indicator/linreg: LinReg indicator --- pkg/indicator/linreg.go | 98 +++++++++++++++++++++++++++++++ pkg/indicator/linreg_callbacks.go | 15 +++++ 2 files changed, 113 insertions(+) create mode 100644 pkg/indicator/linreg.go create mode 100644 pkg/indicator/linreg_callbacks.go diff --git a/pkg/indicator/linreg.go b/pkg/indicator/linreg.go new file mode 100644 index 000000000..e48c82a2c --- /dev/null +++ b/pkg/indicator/linreg.go @@ -0,0 +1,98 @@ +package indicator + +import ( + "github.com/sirupsen/logrus" + "time" + + "github.com/c9s/bbgo/pkg/datatype/floats" + "github.com/c9s/bbgo/pkg/types" +) + +var log = logrus.WithField("indicator", "supertrend") + +// LinReg is Linear Regression baseline +//go:generate callbackgen -type LinReg +type LinReg struct { + types.SeriesBase + types.IntervalWindow + + // Values are the slopes of linear regression baseline + Values floats.Slice + klines types.KLineWindow + + EndTime time.Time + UpdateCallbacks []func(value float64) +} + +// Last slope of linear regression baseline +func (lr *LinReg) Last() float64 { + if lr.Values.Length() == 0 { + return 0.0 + } + return lr.Values.Last() +} + +// Index returns the slope of specified index +func (lr *LinReg) Index(i int) float64 { + if i >= lr.Values.Length() { + return 0.0 + } + + return lr.Values.Index(i) +} + +// Length of the slope values +func (lr *LinReg) Length() int { + return lr.Values.Length() +} + +var _ types.SeriesExtend = &LinReg{} + +// Update Linear Regression baseline slope +func (lr *LinReg) Update(kline types.KLine) { + lr.klines.Add(kline) + lr.klines.Truncate(lr.Window) + if len(lr.klines) < lr.Window { + lr.Values.Push(0) + return + } + + var sumX, sumY, sumXSqr, sumXY float64 = 0, 0, 0, 0 + end := len(lr.klines) - 1 // The last kline + for i := end; i >= end-lr.Window+1; i-- { + val := lr.klines[i].GetClose().Float64() + per := float64(end - i + 1) + sumX += per + sumY += val + sumXSqr += per * per + sumXY += val * per + } + length := float64(lr.Window) + slope := (length*sumXY - sumX*sumY) / (length*sumXSqr - sumX*sumX) + average := sumY / length + endPrice := average - slope*sumX/length + slope + startPrice := endPrice + slope*(length-1) + lr.Values.Push((endPrice - startPrice) / (length - 1)) + + log.Debugf("linear regression baseline slope: %f", lr.Last()) +} + +func (lr *LinReg) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) { + target.OnKLineClosed(types.KLineWith(symbol, interval, lr.PushK)) +} + +func (lr *LinReg) PushK(k types.KLine) { + var zeroTime = time.Time{} + if lr.EndTime != zeroTime && k.EndTime.Before(lr.EndTime) { + return + } + + lr.Update(k) + lr.EndTime = k.EndTime.Time() +} + +func (lr *LinReg) LoadK(allKLines []types.KLine) { + for _, k := range allKLines { + lr.PushK(k) + } +} diff --git a/pkg/indicator/linreg_callbacks.go b/pkg/indicator/linreg_callbacks.go new file mode 100644 index 000000000..c719c8f51 --- /dev/null +++ b/pkg/indicator/linreg_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type LinReg"; DO NOT EDIT. + +package indicator + +import () + +func (lr *LinReg) OnUpdate(cb func(value float64)) { + lr.UpdateCallbacks = append(lr.UpdateCallbacks, cb) +} + +func (lr *LinReg) EmitUpdate(value float64) { + for _, cb := range lr.UpdateCallbacks { + cb(value) + } +}