From 70f4676340467930bbca9a657685058f2554f84f Mon Sep 17 00:00:00 2001 From: zenix Date: Wed, 29 Jun 2022 21:49:02 +0900 Subject: [PATCH] feature: extend indicators, extend seriesbase methods --- pkg/indicator/ad.go | 6 ++- pkg/indicator/alma.go | 22 ++++++---- pkg/indicator/atr.go | 4 +- pkg/indicator/boll.go | 16 +++---- pkg/indicator/cci.go | 4 +- pkg/indicator/cma.go | 7 ++- pkg/indicator/dema.go | 4 +- pkg/indicator/dmi.go | 12 +++--- pkg/indicator/drift.go | 4 +- pkg/indicator/emv.go | 4 +- pkg/indicator/ewma.go | 4 +- pkg/indicator/hull.go | 4 +- pkg/indicator/line.go | 7 ++- pkg/indicator/macd.go | 9 ++-- pkg/indicator/obv.go | 11 +++++ pkg/indicator/rma.go | 4 +- pkg/indicator/rsi.go | 6 ++- pkg/indicator/sma.go | 6 ++- pkg/indicator/ssf.go | 5 ++- pkg/indicator/supertrend.go | 7 ++- pkg/indicator/tema.go | 4 +- pkg/indicator/till.go | 2 + pkg/indicator/tma.go | 4 +- pkg/indicator/vidya.go | 4 +- pkg/indicator/volatility.go | 17 ++++++++ pkg/indicator/vwap.go | 6 ++- pkg/indicator/vwma.go | 7 ++- pkg/indicator/wwma.go | 4 +- pkg/indicator/zlema.go | 4 +- pkg/strategy/ewoDgtrd/strategy.go | 36 ++++++++-------- pkg/types/indicator.go | 71 +++++++++++++++++-------------- pkg/types/seriesbase_imp.go | 51 +++++++++++++++++++--- 32 files changed, 253 insertions(+), 103 deletions(-) diff --git a/pkg/indicator/ad.go b/pkg/indicator/ad.go index d7263a5ab7..6cdf0e3d90 100644 --- a/pkg/indicator/ad.go +++ b/pkg/indicator/ad.go @@ -14,6 +14,7 @@ Accumulation/Distribution Indicator (A/D) */ //go:generate callbackgen -type AD type AD struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice PrePrice float64 @@ -23,6 +24,9 @@ type AD struct { } func (inc *AD) Update(high, low, cloze, volume float64) { + if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc + } var moneyFlowVolume float64 if high == low { moneyFlowVolume = 0 @@ -53,7 +57,7 @@ func (inc *AD) Length() int { return len(inc.Values) } -var _ types.Series = &AD{} +var _ types.SeriesExtend = &AD{} func (inc *AD) calculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { diff --git a/pkg/indicator/alma.go b/pkg/indicator/alma.go index 44e0f18c7f..d9a03ad0a3 100644 --- a/pkg/indicator/alma.go +++ b/pkg/indicator/alma.go @@ -13,11 +13,12 @@ import ( // @param sigma: the standard deviation applied to the combo line. This makes the combo line sharper //go:generate callbackgen -type ALMA type ALMA struct { + types.SeriesBase types.IntervalWindow // required Offset float64 // required: recommend to be 5 Sigma int // required: recommend to be 0.5 - Weight []float64 - Sum float64 + weight []float64 + sum float64 input []float64 Values types.Float64Slice UpdateCallbacks []func(value float64) @@ -27,16 +28,17 @@ const MaxNumOfALMA = 5_000 const MaxNumOfALMATruncateSize = 100 func (inc *ALMA) Update(value float64) { - if inc.Weight == nil { - inc.Weight = make([]float64, inc.Window) + if inc.weight == nil { + inc.SeriesBase.Series = inc + inc.weight = make([]float64, inc.Window) m := inc.Offset * (float64(inc.Window) - 1.) s := float64(inc.Window) / float64(inc.Sigma) - inc.Sum = 0. + inc.sum = 0. for i := 0; i < inc.Window; i++ { diff := float64(i) - m wt := math.Exp(-diff * diff / 2. / s / s) - inc.Sum += wt - inc.Weight[i] = wt + inc.sum += wt + inc.weight[i] = wt } } inc.input = append(inc.input, value) @@ -44,9 +46,9 @@ func (inc *ALMA) Update(value float64) { weightedSum := 0.0 inc.input = inc.input[len(inc.input)-inc.Window:] for i := 0; i < inc.Window; i++ { - weightedSum += inc.Weight[inc.Window-i-1] * inc.input[i] + weightedSum += inc.weight[inc.Window-i-1] * inc.input[i] } - inc.Values.Push(weightedSum / inc.Sum) + inc.Values.Push(weightedSum / inc.sum) if len(inc.Values) > MaxNumOfALMA { inc.Values = inc.Values[MaxNumOfALMATruncateSize-1:] } @@ -71,6 +73,8 @@ func (inc *ALMA) Length() int { return len(inc.Values) } +var _ types.SeriesExtend = &ALMA{} + func (inc *ALMA) calculateAndUpdate(allKLines []types.KLine) { if inc.input == nil { for _, k := range allKLines { diff --git a/pkg/indicator/atr.go b/pkg/indicator/atr.go index 327a302633..759cffebe9 100644 --- a/pkg/indicator/atr.go +++ b/pkg/indicator/atr.go @@ -9,6 +9,7 @@ import ( //go:generate callbackgen -type ATR type ATR struct { + types.SeriesBase types.IntervalWindow PercentageVolatility types.Float64Slice @@ -25,6 +26,7 @@ func (inc *ATR) Update(high, low, cloze float64) { } if inc.RMA == nil { + inc.SeriesBase.Series = inc inc.RMA = &RMA{ IntervalWindow: types.IntervalWindow{Window: inc.Window}, Adjust: true, @@ -73,7 +75,7 @@ func (inc *ATR) Length() int { return inc.RMA.Length() } -var _ types.Series = &ATR{} +var _ types.SeriesExtend = &ATR{} func (inc *ATR) CalculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { diff --git a/pkg/indicator/boll.go b/pkg/indicator/boll.go index 70338be04f..30ebf8466e 100644 --- a/pkg/indicator/boll.go +++ b/pkg/indicator/boll.go @@ -41,20 +41,20 @@ type BOLL struct { type BandType int -func (inc *BOLL) GetUpBand() types.Series { - return &inc.UpBand +func (inc *BOLL) GetUpBand() types.SeriesExtend { + return types.NewSeries(&inc.UpBand) } -func (inc *BOLL) GetDownBand() types.Series { - return &inc.DownBand +func (inc *BOLL) GetDownBand() types.SeriesExtend { + return types.NewSeries(&inc.DownBand) } -func (inc *BOLL) GetSMA() types.Series { - return &inc.SMA +func (inc *BOLL) GetSMA() types.SeriesExtend { + return types.NewSeries(&inc.SMA) } -func (inc *BOLL) GetStdDev() types.Series { - return &inc.StdDev +func (inc *BOLL) GetStdDev() types.SeriesExtend { + return types.NewSeries(&inc.StdDev) } func (inc *BOLL) LastUpBand() float64 { diff --git a/pkg/indicator/cci.go b/pkg/indicator/cci.go index 9380ad816b..ef0ec558d9 100644 --- a/pkg/indicator/cci.go +++ b/pkg/indicator/cci.go @@ -12,6 +12,7 @@ import ( // with modification of ddof=0 to let standard deviation to be divided by N instead of N-1 //go:generate callbackgen -type CCI type CCI struct { + types.SeriesBase types.IntervalWindow Input types.Float64Slice TypicalPrice types.Float64Slice @@ -23,6 +24,7 @@ type CCI struct { func (inc *CCI) Update(value float64) { if len(inc.TypicalPrice) == 0 { + inc.SeriesBase.Series = inc inc.TypicalPrice.Push(value) inc.Input.Push(value) return @@ -75,7 +77,7 @@ func (inc *CCI) Length() int { return len(inc.Values) } -var _ types.Series = &CCI{} +var _ types.SeriesExtend = &CCI{} var three = fixedpoint.NewFromInt(3) diff --git a/pkg/indicator/cma.go b/pkg/indicator/cma.go index 8040c87072..fbdf357349 100644 --- a/pkg/indicator/cma.go +++ b/pkg/indicator/cma.go @@ -8,6 +8,7 @@ import ( // Refer: https://en.wikipedia.org/wiki/Moving_average //go:generate callbackgen -type CA type CA struct { + types.SeriesBase Interval types.Interval Values types.Float64Slice length float64 @@ -15,11 +16,15 @@ type CA struct { } func (inc *CA) Update(x float64) { + if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc + } newVal := (inc.Values.Last()*inc.length + x) / (inc.length + 1.) inc.length += 1 inc.Values.Push(newVal) if len(inc.Values) > MaxNumOfEWMA { inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] + inc.length = float64(len(inc.Values)) } } @@ -41,7 +46,7 @@ func (inc *CA) Length() int { return len(inc.Values) } -var _ types.Series = &CA{} +var _ types.SeriesExtend = &CA{} func (inc *CA) calculateAndUpdate(allKLines []types.KLine) { for _, k := range allKLines { diff --git a/pkg/indicator/dema.go b/pkg/indicator/dema.go index bc476134a2..d9152d2791 100644 --- a/pkg/indicator/dema.go +++ b/pkg/indicator/dema.go @@ -10,6 +10,7 @@ import ( //go:generate callbackgen -type DEMA type DEMA struct { types.IntervalWindow + types.SeriesBase Values types.Float64Slice a1 *EWMA a2 *EWMA @@ -19,6 +20,7 @@ type DEMA struct { func (inc *DEMA) Update(value float64) { if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc inc.a1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} inc.a2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} } @@ -46,7 +48,7 @@ func (inc *DEMA) Length() int { return len(inc.Values) } -var _ types.Series = &DEMA{} +var _ types.SeriesExtend = &DEMA{} func (inc *DEMA) calculateAndUpdate(allKLines []types.KLine) { if inc.a1 == nil { diff --git a/pkg/indicator/dmi.go b/pkg/indicator/dmi.go index cb0fc71691..74ea75eaa7 100644 --- a/pkg/indicator/dmi.go +++ b/pkg/indicator/dmi.go @@ -17,11 +17,11 @@ type DMI struct { types.IntervalWindow ADXSmoothing int atr *ATR - DMP types.UpdatableSeries - DMN types.UpdatableSeries + DMP types.UpdatableSeriesExtend + DMN types.UpdatableSeriesExtend DIPlus *types.Queue DIMinus *types.Queue - ADX types.UpdatableSeries + ADX types.UpdatableSeriesExtend PrevHigh, PrevLow float64 UpdateCallbacks []func(diplus, diminus, adx float64) } @@ -71,15 +71,15 @@ func (inc *DMI) Update(high, low, cloze float64) { } -func (inc *DMI) GetDIPlus() types.Series { +func (inc *DMI) GetDIPlus() types.SeriesExtend { return inc.DIPlus } -func (inc *DMI) GetDIMinus() types.Series { +func (inc *DMI) GetDIMinus() types.SeriesExtend { return inc.DIMinus } -func (inc *DMI) GetADX() types.Series { +func (inc *DMI) GetADX() types.SeriesExtend { return inc.ADX } diff --git a/pkg/indicator/drift.go b/pkg/indicator/drift.go index bda5b51d5f..8494acc398 100644 --- a/pkg/indicator/drift.go +++ b/pkg/indicator/drift.go @@ -11,6 +11,7 @@ import ( // could be used in Monte Carlo Simulations //go:generate callbackgen -type Drift type Drift struct { + types.SeriesBase types.IntervalWindow chng *types.Queue Values types.Float64Slice @@ -22,6 +23,7 @@ type Drift struct { func (inc *Drift) Update(value float64) { if inc.chng == nil { + inc.SeriesBase.Series = inc inc.SMA = &SMA{IntervalWindow: types.IntervalWindow{Interval: inc.Interval, Window: inc.Window}} inc.chng = types.NewQueue(inc.Window) inc.LastValue = value @@ -64,7 +66,7 @@ func (inc *Drift) Length() int { return inc.Values.Length() } -var _ types.Series = &Drift{} +var _ types.SeriesExtend = &Drift{} func (inc *Drift) calculateAndUpdate(allKLines []types.KLine) { if inc.chng == nil { diff --git a/pkg/indicator/emv.go b/pkg/indicator/emv.go index 08d439e45b..f626ad70eb 100644 --- a/pkg/indicator/emv.go +++ b/pkg/indicator/emv.go @@ -9,6 +9,7 @@ import ( //go:generate callbackgen -type EMV type EMV struct { + types.SeriesBase types.IntervalWindow prevH float64 prevL float64 @@ -25,6 +26,7 @@ func (inc *EMV) Update(high, low, vol float64) { inc.EMVScale = DefaultEMVScale } if inc.prevH == 0 || inc.Values == nil { + inc.SeriesBase.Series = inc inc.prevH = high inc.prevL = low inc.Values = &SMA{IntervalWindow: inc.IntervalWindow} @@ -59,7 +61,7 @@ func (inc *EMV) Length() int { return inc.Values.Length() } -var _ types.Series = &EMV{} +var _ types.SeriesExtend = &EMV{} func (inc *EMV) calculateAndUpdate(allKLines []types.KLine) { if inc.Values == nil { diff --git a/pkg/indicator/ewma.go b/pkg/indicator/ewma.go index d94fb7953e..4335c13169 100644 --- a/pkg/indicator/ewma.go +++ b/pkg/indicator/ewma.go @@ -16,6 +16,7 @@ const MaxNumOfEWMATruncateSize = 100 //go:generate callbackgen -type EWMA type EWMA struct { types.IntervalWindow + types.SeriesBase Values types.Float64Slice LastOpenTime time.Time @@ -26,6 +27,7 @@ func (inc *EWMA) Update(value float64) { var multiplier = 2.0 / float64(1+inc.Window) if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc inc.Values.Push(value) return } else if len(inc.Values) > MaxNumOfEWMA { @@ -136,4 +138,4 @@ func (inc *EWMA) Bind(updater KLineWindowUpdater) { updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) } -var _ types.Series = &EWMA{} +var _ types.SeriesExtend = &EWMA{} diff --git a/pkg/indicator/hull.go b/pkg/indicator/hull.go index 0c8347f9b5..7eaf8ad70a 100644 --- a/pkg/indicator/hull.go +++ b/pkg/indicator/hull.go @@ -10,6 +10,7 @@ import ( // Refer URL: https://fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/hull-moving-average //go:generate callbackgen -type HULL type HULL struct { + types.SeriesBase types.IntervalWindow ma1 *EWMA ma2 *EWMA @@ -20,6 +21,7 @@ type HULL struct { func (inc *HULL) Update(value float64) { if inc.result == nil { + inc.SeriesBase.Series = inc inc.ma1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window / 2}} inc.ma2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} inc.result = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, int(math.Sqrt(float64(inc.Window)))}} @@ -50,7 +52,7 @@ func (inc *HULL) Length() int { return inc.result.Length() } -var _ types.Series = &HULL{} +var _ types.SeriesExtend = &HULL{} // TODO: should we just ignore the possible overlapping? func (inc *HULL) calculateAndUpdate(allKLines []types.KLine) { diff --git a/pkg/indicator/line.go b/pkg/indicator/line.go index 763d58f89e..a3932ac1c0 100644 --- a/pkg/indicator/line.go +++ b/pkg/indicator/line.go @@ -12,6 +12,7 @@ import ( // 3. resistance // of the market data, defined with series interface type Line struct { + types.SeriesBase types.IntervalWindow start float64 end float64 @@ -63,7 +64,7 @@ func (l *Line) SetXY2(index int, value float64) { } func NewLine(startIndex int, startValue float64, endIndex int, endValue float64, interval types.Interval) *Line { - return &Line{ + line := &Line{ start: startValue, end: endValue, startIndex: startIndex, @@ -71,6 +72,8 @@ func NewLine(startIndex int, startValue float64, endIndex int, endValue float64, currentTime: time.Time{}, Interval: interval, } + line.SeriesBase.Series = line + return line } -var _ types.Series = &Line{} +var _ types.SeriesExtend = &Line{} diff --git a/pkg/indicator/macd.go b/pkg/indicator/macd.go index 3dfbd6d450..43e94589bb 100644 --- a/pkg/indicator/macd.go +++ b/pkg/indicator/macd.go @@ -87,6 +87,7 @@ func (inc *MACD) Bind(updater KLineWindowUpdater) { } type MACDValues struct { + types.SeriesBase *MACD } @@ -109,10 +110,12 @@ func (inc *MACDValues) Length() int { return len(inc.Values) } -func (inc *MACD) MACD() types.Series { - return &MACDValues{inc} +func (inc *MACD) MACD() types.SeriesExtend { + out := &MACDValues{MACD: inc} + out.SeriesBase.Series = out + return out } -func (inc *MACD) Singals() types.Series { +func (inc *MACD) Singals() types.SeriesExtend { return &inc.SignalLine } diff --git a/pkg/indicator/obv.go b/pkg/indicator/obv.go index 3ea11772da..52321892ff 100644 --- a/pkg/indicator/obv.go +++ b/pkg/indicator/obv.go @@ -14,6 +14,7 @@ On-Balance Volume (OBV) Definition */ //go:generate callbackgen -type OBV type OBV struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice PrePrice float64 @@ -24,6 +25,7 @@ type OBV struct { func (inc *OBV) Update(price, volume float64) { if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc inc.PrePrice = price inc.Values.Push(volume) return @@ -43,6 +45,15 @@ func (inc *OBV) Last() float64 { return inc.Values[len(inc.Values)-1] } +func (inc *OBV) Index(i int) float64 { + if len(inc.Values)-i <= 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-i-1] +} + +var _ types.SeriesExtend = &OBV{} + func (inc *OBV) calculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { diff --git a/pkg/indicator/rma.go b/pkg/indicator/rma.go index 8fee7a1283..857261ecc9 100644 --- a/pkg/indicator/rma.go +++ b/pkg/indicator/rma.go @@ -11,6 +11,7 @@ import ( // Refer: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html#pandas-dataframe-ewm //go:generate callbackgen -type RMA type RMA struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice counter int @@ -24,6 +25,7 @@ type RMA struct { func (inc *RMA) Update(x float64) { lambda := 1 / float64(inc.Window) if inc.counter == 0 { + inc.SeriesBase.Series = inc inc.sum = 1 inc.tmp = x } else { @@ -60,7 +62,7 @@ func (inc *RMA) Length() int { return len(inc.Values) } -var _ types.Series = &RMA{} +var _ types.SeriesExtend = &RMA{} func (inc *RMA) calculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { diff --git a/pkg/indicator/rsi.go b/pkg/indicator/rsi.go index b9eabd6f48..8a76322abd 100644 --- a/pkg/indicator/rsi.go +++ b/pkg/indicator/rsi.go @@ -14,6 +14,7 @@ https://www.investopedia.com/terms/r/rsi.asp */ //go:generate callbackgen -type RSI type RSI struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice Prices types.Float64Slice @@ -25,6 +26,9 @@ type RSI struct { } func (inc *RSI) Update(price float64) { + if len(inc.Prices) == 0 { + inc.SeriesBase.Series = inc + } inc.Prices.Push(price) if len(inc.Prices) < inc.Window+1 { @@ -74,7 +78,7 @@ func (inc *RSI) Length() int { return len(inc.Values) } -var _ types.Series = &RSI{} +var _ types.SeriesExtend = &RSI{} func (inc *RSI) calculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { diff --git a/pkg/indicator/sma.go b/pkg/indicator/sma.go index d500c5d6fa..c7e11ac091 100644 --- a/pkg/indicator/sma.go +++ b/pkg/indicator/sma.go @@ -16,6 +16,7 @@ var zeroTime time.Time //go:generate callbackgen -type SMA type SMA struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice Cache types.Float64Slice @@ -44,10 +45,13 @@ func (inc *SMA) Length() int { return len(inc.Values) } -var _ types.Series = &SMA{} +var _ types.SeriesExtend = &SMA{} func (inc *SMA) Update(value float64) { if len(inc.Cache) < inc.Window { + if len(inc.Cache) == 0 { + inc.SeriesBase.Series = inc + } inc.Cache = append(inc.Cache, value) if len(inc.Cache) == inc.Window { inc.Values = append(inc.Values, types.Mean(&inc.Cache)) diff --git a/pkg/indicator/ssf.go b/pkg/indicator/ssf.go index d8c1340d46..28871a041a 100644 --- a/pkg/indicator/ssf.go +++ b/pkg/indicator/ssf.go @@ -20,6 +20,7 @@ import ( // //go:generate callbackgen -type SSF type SSF struct { + types.SeriesBase types.IntervalWindow Poles int c1 float64 @@ -34,6 +35,7 @@ type SSF struct { func (inc *SSF) Update(value float64) { if inc.Poles == 3 { if inc.Values == nil { + inc.SeriesBase.Series = inc x := math.Pi / float64(inc.Window) a0 := math.Exp(-x) b0 := 2. * a0 * math.Cos(math.Sqrt(3.)*x) @@ -53,6 +55,7 @@ func (inc *SSF) Update(value float64) { inc.Values.Push(result) } else { // poles == 2 if inc.Values == nil { + inc.SeriesBase.Series = inc x := math.Pi * math.Sqrt(2.) / float64(inc.Window) a0 := math.Exp(-x) inc.c3 = -a0 * a0 @@ -88,7 +91,7 @@ func (inc *SSF) Last() float64 { return inc.Values.Last() } -var _ types.Series = &SSF{} +var _ types.SeriesExtend = &SSF{} func (inc *SSF) calculateAndUpdate(allKLines []types.KLine) { if inc.Values != nil { diff --git a/pkg/indicator/supertrend.go b/pkg/indicator/supertrend.go index c195dc4e65..d87b41b59c 100644 --- a/pkg/indicator/supertrend.go +++ b/pkg/indicator/supertrend.go @@ -12,6 +12,7 @@ var logst = logrus.WithField("indicator", "supertrend") //go:generate callbackgen -type Supertrend type Supertrend struct { + types.SeriesBase types.IntervalWindow ATRMultiplier float64 `json:"atrMultiplier"` @@ -54,6 +55,10 @@ func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) { panic("window must be greater than 0") } + if inc.AverageTrueRange == nil { + inc.SeriesBase.Series = inc + } + // Start with DirectionUp if inc.trend != types.DirectionUp && inc.trend != types.DirectionDown { inc.trend = types.DirectionUp @@ -120,7 +125,7 @@ func (inc *Supertrend) GetSignal() types.Direction { return inc.tradeSignal } -var _ types.Series = &Supertrend{} +var _ types.SeriesExtend = &Supertrend{} func (inc *Supertrend) calculateAndUpdate(kLines []types.KLine) { for _, k := range kLines { diff --git a/pkg/indicator/tema.go b/pkg/indicator/tema.go index 91d53a63d0..8d1fc3fd3c 100644 --- a/pkg/indicator/tema.go +++ b/pkg/indicator/tema.go @@ -9,6 +9,7 @@ import ( //go:generate callbackgen -type TEMA type TEMA struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice A1 *EWMA @@ -20,6 +21,7 @@ type TEMA struct { func (inc *TEMA) Update(value float64) { if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc inc.A1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} inc.A2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} inc.A3 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} @@ -51,7 +53,7 @@ func (inc *TEMA) Length() int { return len(inc.Values) } -var _ types.Series = &TEMA{} +var _ types.SeriesExtend = &TEMA{} func (inc *TEMA) calculateAndUpdate(allKLines []types.KLine) { if inc.A1 == nil { diff --git a/pkg/indicator/till.go b/pkg/indicator/till.go index 73f97ead50..795194e5cb 100644 --- a/pkg/indicator/till.go +++ b/pkg/indicator/till.go @@ -10,6 +10,7 @@ const defaultVolumeFactor = 0.7 // Refer URL: https://tradingpedia.com/forex-trading-indicator/t3-moving-average-indicator/ //go:generate callbackgen -type TILL type TILL struct { + types.SeriesBase types.IntervalWindow VolumeFactor float64 e1 *EWMA @@ -30,6 +31,7 @@ func (inc *TILL) Update(value float64) { if inc.VolumeFactor == 0 { inc.VolumeFactor = defaultVolumeFactor } + inc.SeriesBase.Series = inc inc.e1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} inc.e2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} inc.e3 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} diff --git a/pkg/indicator/tma.go b/pkg/indicator/tma.go index 482f3936cd..c100a987d8 100644 --- a/pkg/indicator/tma.go +++ b/pkg/indicator/tma.go @@ -8,6 +8,7 @@ import ( // Refer URL: https://ja.wikipedia.org/wiki/移動平均 //go:generate callbackgen -type TMA type TMA struct { + types.SeriesBase types.IntervalWindow s1 *SMA s2 *SMA @@ -16,6 +17,7 @@ type TMA struct { func (inc *TMA) Update(value float64) { if inc.s1 == nil { + inc.SeriesBase.Series = inc w := (inc.Window + 1) / 2 inc.s1 = &SMA{IntervalWindow: types.IntervalWindow{inc.Interval, w}} inc.s2 = &SMA{IntervalWindow: types.IntervalWindow{inc.Interval, w}} @@ -46,7 +48,7 @@ func (inc *TMA) Length() int { return inc.s2.Length() } -var _ types.Series = &TMA{} +var _ types.SeriesExtend = &TMA{} func (inc *TMA) calculateAndUpdate(allKLines []types.KLine) { if inc.s1 == nil { diff --git a/pkg/indicator/vidya.go b/pkg/indicator/vidya.go index 658e89ac11..cda286e00b 100644 --- a/pkg/indicator/vidya.go +++ b/pkg/indicator/vidya.go @@ -10,6 +10,7 @@ import ( // Refer URL: https://metatrader5.com/en/terminal/help/indicators/trend_indicators/vida //go:generate callbackgen -type VIDYA type VIDYA struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice input types.Float64Slice @@ -19,6 +20,7 @@ type VIDYA struct { func (inc *VIDYA) Update(value float64) { if inc.Values.Length() == 0 { + inc.SeriesBase.Series = inc inc.Values.Push(value) inc.input.Push(value) return @@ -66,7 +68,7 @@ func (inc *VIDYA) Length() int { return inc.Values.Length() } -var _ types.Series = &VIDYA{} +var _ types.SeriesExtend = &VIDYA{} func (inc *VIDYA) calculateAndUpdate(allKLines []types.KLine) { if inc.input.Length() == 0 { diff --git a/pkg/indicator/volatility.go b/pkg/indicator/volatility.go index aae62e2836..9f4571408e 100644 --- a/pkg/indicator/volatility.go +++ b/pkg/indicator/volatility.go @@ -17,6 +17,7 @@ const MaxNumOfVOLTruncateSize = 100 //go:generate callbackgen -type VOLATILITY type VOLATILITY struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice EndTime time.Time @@ -31,6 +32,19 @@ func (inc *VOLATILITY) Last() float64 { return inc.Values[len(inc.Values)-1] } +func (inc *VOLATILITY) Index(i int) float64 { + if len(inc.Values)-i <= 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-i-1] +} + +func (inc *VOLATILITY) Length() int { + return len(inc.Values) +} + +var _ types.SeriesExtend = &VOLATILITY{} + func (inc *VOLATILITY) calculateAndUpdate(klines []types.KLine) { if len(klines) < inc.Window { return @@ -42,6 +56,9 @@ func (inc *VOLATILITY) calculateAndUpdate(klines []types.KLine) { if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { return } + if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc + } var recentT = klines[end-(inc.Window-1) : end+1] diff --git a/pkg/indicator/vwap.go b/pkg/indicator/vwap.go index 7fcac717aa..89e3b28e92 100644 --- a/pkg/indicator/vwap.go +++ b/pkg/indicator/vwap.go @@ -17,6 +17,7 @@ Volume-Weighted Average Price (VWAP) Explained */ //go:generate callbackgen -type VWAP type VWAP struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice Prices types.Float64Slice @@ -29,6 +30,9 @@ type VWAP struct { } func (inc *VWAP) Update(price, volume float64) { + if len(inc.Prices) == 0 { + inc.SeriesBase.Series = inc + } inc.Prices.Push(price) inc.Volumes.Push(volume) @@ -65,7 +69,7 @@ func (inc *VWAP) Length() int { return len(inc.Values) } -var _ types.Series = &VWAP{} +var _ types.SeriesExtend = &VWAP{} func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) { var priceF = KLineTypicalPriceMapper diff --git a/pkg/indicator/vwma.go b/pkg/indicator/vwma.go index 131e2f5df0..4ee1068c94 100644 --- a/pkg/indicator/vwma.go +++ b/pkg/indicator/vwma.go @@ -20,6 +20,7 @@ Volume Weighted Moving Average */ //go:generate callbackgen -type VWMA type VWMA struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice EndTime time.Time @@ -46,7 +47,7 @@ func (inc *VWMA) Length() int { return len(inc.Values) } -var _ types.Series = &VWMA{} +var _ types.SeriesExtend = &VWMA{} func KLinePriceVolumeMapper(k types.KLine) float64 { return k.Close.Mul(k.Volume).Float64() @@ -81,6 +82,10 @@ func (inc *VWMA) calculateAndUpdate(kLines []types.KLine) { return } + if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc + } + vwma := pv / v inc.Values.Push(vwma) diff --git a/pkg/indicator/wwma.go b/pkg/indicator/wwma.go index 13fd1b8d19..be0ec0a7ed 100644 --- a/pkg/indicator/wwma.go +++ b/pkg/indicator/wwma.go @@ -14,6 +14,7 @@ const MaxNumOfWWMATruncateSize = 100 //go:generate callbackgen -type WWMA type WWMA struct { + types.SeriesBase types.IntervalWindow Values types.Float64Slice LastOpenTime time.Time @@ -23,6 +24,7 @@ type WWMA struct { func (inc *WWMA) Update(value float64) { if len(inc.Values) == 0 { + inc.SeriesBase.Series = inc inc.Values.Push(value) return } else if len(inc.Values) > MaxNumOfWWMA { @@ -85,4 +87,4 @@ func (inc *WWMA) Bind(updater KLineWindowUpdater) { updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) } -var _ types.Series = &WWMA{} +var _ types.SeriesExtend = &WWMA{} diff --git a/pkg/indicator/zlema.go b/pkg/indicator/zlema.go index 4ed97d84ac..f127c0008b 100644 --- a/pkg/indicator/zlema.go +++ b/pkg/indicator/zlema.go @@ -9,6 +9,7 @@ import ( //go:generate callbackgen -type ZLEMA type ZLEMA struct { + types.SeriesBase types.IntervalWindow data types.Float64Slice @@ -41,6 +42,7 @@ func (inc *ZLEMA) Length() int { func (inc *ZLEMA) Update(value float64) { if inc.lag == 0 || inc.zlema == nil { + inc.SeriesBase.Series = inc inc.zlema = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} inc.lag = int((float64(inc.Window)-1.)/2. + 0.5) } @@ -55,7 +57,7 @@ func (inc *ZLEMA) Update(value float64) { inc.zlema.Update(emaData) } -var _ types.Series = &ZLEMA{} +var _ types.SeriesExtend = &ZLEMA{} func (inc *ZLEMA) calculateAndUpdate(allKLines []types.KLine) { if inc.zlema == nil { diff --git a/pkg/strategy/ewoDgtrd/strategy.go b/pkg/strategy/ewoDgtrd/strategy.go index e777bcb9d0..9188dc54c6 100644 --- a/pkg/strategy/ewoDgtrd/strategy.go +++ b/pkg/strategy/ewoDgtrd/strategy.go @@ -63,11 +63,11 @@ type Strategy struct { atr *indicator.ATR emv *indicator.EMV ccis *CCISTOCH - ma5 types.Series - ma34 types.Series - ewo types.Series - ewoSignal types.Series - ewoHistogram types.Series + ma5 types.SeriesExtend + ma34 types.SeriesExtend + ewo types.SeriesExtend + ewoSignal types.SeriesExtend + ewoHistogram types.SeriesExtend ewoChangeRate float64 heikinAshi *HeikinAshi peakPrice fixedpoint.Value @@ -279,8 +279,8 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { }) - s.ma5 = ema5 - s.ma34 = ema34 + s.ma5 = types.NewSeries(ema5) + s.ma34 = types.NewSeries(ema34) } else if s.UseSma { sma5 := &indicator.SMA{IntervalWindow: window5} sma34 := &indicator.SMA{IntervalWindow: window34} @@ -300,8 +300,8 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { sma34.Update(cloze) } }) - s.ma5 = sma5 - s.ma34 = sma34 + s.ma5 = types.NewSeries(sma5) + s.ma34 = types.NewSeries(sma34) } else { evwma5 := &VWEMA{ PV: &indicator.EWMA{IntervalWindow: window5}, @@ -331,12 +331,12 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { evwma34.UpdateVal(price, vol) } }) - s.ma5 = evwma5 - s.ma34 = evwma34 + s.ma5 = types.NewSeries(evwma5) + s.ma34 = types.NewSeries(evwma34) } - s.ewo = types.Mul(types.Minus(types.Div(s.ma5, s.ma34), 1.0), 100.) - s.ewoHistogram = types.Minus(s.ma5, s.ma34) + s.ewo = s.ma5.Div(s.ma34).Minus(1.0).Mul(100.) + s.ewoHistogram = s.ma5.Minus(s.ma34) windowSignal := types.IntervalWindow{Interval: s.Interval, Window: s.SignalWindow} if s.UseEma { sig := &indicator.EWMA{IntervalWindow: windowSignal} @@ -355,7 +355,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { sig.Update(s.ewo.Last()) } }) - s.ewoSignal = sig + s.ewoSignal = types.NewSeries(sig) } else if s.UseSma { sig := &indicator.SMA{IntervalWindow: windowSignal} store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) { @@ -365,7 +365,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { if sig.Length() == 0 { // lazy init - ewoVals := types.Reverse(s.ewo) + ewoVals := s.ewo.Reverse() for _, ewoValue := range ewoVals { sig.Update(ewoValue) } @@ -373,7 +373,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { sig.Update(s.ewo.Last()) } }) - s.ewoSignal = sig + s.ewoSignal = types.NewSeries(sig) } else { sig := &VWEMA{ PV: &indicator.EWMA{IntervalWindow: windowSignal}, @@ -385,7 +385,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { } if sig.Length() == 0 { // lazy init - ewoVals := types.Reverse(s.ewo) + ewoVals := s.ewo.Reverse() for i, ewoValue := range ewoVals { vol := window.Volume().Index(i) sig.PV.Update(ewoValue * vol) @@ -397,7 +397,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { sig.V.Update(vol) } }) - s.ewoSignal = sig + s.ewoSignal = types.NewSeries(sig) } } diff --git a/pkg/types/indicator.go b/pkg/types/indicator.go index 3790c60751..a6dea330fd 100644 --- a/pkg/types/indicator.go +++ b/pkg/types/indicator.go @@ -11,15 +11,18 @@ import ( // Super basic Series type that simply holds the float64 data // with size limit (the only difference compare to float64slice) type Queue struct { + SeriesBase arr []float64 size int } func NewQueue(size int) *Queue { - return &Queue{ + out := &Queue{ arr: make([]float64, 0, size), size: size, } + out.SeriesBase.Series = out + return out } func (inc *Queue) Last() float64 { @@ -47,7 +50,7 @@ func (inc *Queue) Update(v float64) { } } -var _ Series = &Queue{} +var _ SeriesExtend = &Queue{} // Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data. type Float64Indicator interface { @@ -82,24 +85,24 @@ type SeriesExtend interface { Array(limit ...int) (result []float64) Reverse(limit ...int) (result Float64Slice) Change(offset ...int) SeriesExtend - Stdev(length int) float64 + PercentageChange(offset ...int) SeriesExtend + Stdev(params ...int) float64 + Rolling(window int) *RollingResult + Shift(offset int) SeriesExtend + Skew(length int) float64 + Variance(length int) float64 + Covariance(b Series, length int) float64 + Correlation(b Series, length int, method ...CorrFunc) float64 + Rank(length int) SeriesExtend } -type IndexFuncType func(int) float64 -type LastFuncType func() float64 -type LengthFuncType func() int - type SeriesBase struct { - index IndexFuncType - last LastFuncType - length LengthFuncType + Series } func NewSeries(a Series) SeriesExtend { return &SeriesBase{ - index: a.Index, - last: a.Last, - length: a.Length, + Series: a, } } @@ -108,6 +111,11 @@ type UpdatableSeries interface { Update(float64) } +type UpdatableSeriesExtend interface { + SeriesExtend + Update(float64) +} + // The interface maps to pinescript basic type `series` for bool type // Access the internal historical data from the latest to the oldest // Index(0) always maps to Last() @@ -624,13 +632,13 @@ func (c *PercentageChangeResult) Length() int { // Percentage change between current and a prior element, a / a[offset] - 1. // offset: if not give, offset is 1. -func PercentageChange(a Series, offset ...int) Series { +func PercentageChange(a Series, offset ...int) SeriesExtend { o := 1 if len(offset) > 0 { o = offset[0] } - return &PercentageChangeResult{a, o} + return NewSeries(&PercentageChangeResult{a, o}) } func Stdev(a Series, params ...int) float64 { @@ -650,7 +658,7 @@ func Stdev(a Series, params ...int) float64 { diff := a.Index(i) - avg s += diff * diff } - return math.Sqrt(s / float64(length - ddof)) + return math.Sqrt(s / float64(length-ddof)) } type CorrFunc func(Series, Series, int) float64 @@ -678,7 +686,7 @@ func Kendall(a, b Series, length int) float64 { return float64(concordant-discordant) * 2.0 / float64(length*(length-1)) } -func Rank(a Series, length int) Series { +func Rank(a Series, length int) SeriesExtend { if length > a.Length() { length = a.Length() } @@ -805,10 +813,10 @@ func (inc *ShiftResult) Last() float64 { return inc.a.Index(inc.offset) } func (inc *ShiftResult) Index(i int) float64 { - if inc.offset + i < 0 { + if inc.offset+i < 0 { return 0 } - if inc.offset + i > inc.a.Length() { + if inc.offset+i > inc.a.Length() { return 0 } return inc.a.Index(inc.offset + i) @@ -818,18 +826,18 @@ func (inc *ShiftResult) Length() int { return inc.a.Length() - inc.offset } -func Shift(a Series, offset int) Series { - return &ShiftResult{a, offset} +func Shift(a Series, offset int) SeriesExtend { + return NewSeries(&ShiftResult{a, offset}) } type RollingResult struct { - a Series + a Series window int } type SliceView struct { - a Series - start int + a Series + start int length int } @@ -840,7 +848,7 @@ func (s *SliceView) Index(i int) float64 { if i >= s.length { return 0 } - return s.a.Index(i+s.start) + return s.a.Index(i + s.start) } func (s *SliceView) Length() int { @@ -849,21 +857,21 @@ func (s *SliceView) Length() int { var _ Series = &SliceView{} -func (r *RollingResult) Last() Series { - return &SliceView{r.a, 0, r.window} +func (r *RollingResult) Last() SeriesExtend { + return NewSeries(&SliceView{r.a, 0, r.window}) } -func (r *RollingResult) Index(i int) Series { - if i * r.window > r.a.Length() { +func (r *RollingResult) Index(i int) SeriesExtend { + if i*r.window > r.a.Length() { return nil } - return &SliceView{r.a, i*r.window, r.window} + return NewSeries(&SliceView{r.a, i * r.window, r.window}) } func (r *RollingResult) Length() int { mod := r.a.Length() % r.window if mod > 0 { - return r.a.Length() / r.window + 1 + return r.a.Length()/r.window + 1 } else { return r.a.Length() / r.window } @@ -872,4 +880,5 @@ func (r *RollingResult) Length() int { func Rolling(a Series, window int) *RollingResult { return &RollingResult{a, window} } + // TODO: ta.linreg diff --git a/pkg/types/seriesbase_imp.go b/pkg/types/seriesbase_imp.go index 021da9aca9..f15317a728 100644 --- a/pkg/types/seriesbase_imp.go +++ b/pkg/types/seriesbase_imp.go @@ -1,15 +1,24 @@ package types func (s *SeriesBase) Index(i int) float64 { - return s.index(i) + if s.Series == nil { + return 0 + } + return s.Series.Index(i) } func (s *SeriesBase) Last() float64 { - return s.last() + if s.Series == nil { + return 0 + } + return s.Series.Last() } func (s *SeriesBase) Length() int { - return s.length() + if s.Series == nil { + return 0 + } + return s.Series.Length() } func (s *SeriesBase) Sum(limit ...int) float64 { @@ -80,6 +89,38 @@ func (s *SeriesBase) Change(offset ...int) SeriesExtend { return Change(s, offset...) } -func (s *SeriesBase) Stdev(length int) float64 { - return Stdev(s, length) +func (s *SeriesBase) PercentageChange(offset ...int) SeriesExtend { + return PercentageChange(s, offset...) +} + +func (s *SeriesBase) Stdev(params ...int) float64 { + return Stdev(s, params...) +} + +func (s *SeriesBase) Rolling(window int) *RollingResult { + return Rolling(s, window) +} + +func (s *SeriesBase) Shift(offset int) SeriesExtend { + return Shift(s, offset) +} + +func (s *SeriesBase) Skew(length int) float64 { + return Skew(s, length) +} + +func (s *SeriesBase) Variance(length int) float64 { + return Variance(s, length) +} + +func (s *SeriesBase) Covariance(b Series, length int) float64 { + return Covariance(s, b, length) +} + +func (s *SeriesBase) Correlation(b Series, length int, method ...CorrFunc) float64 { + return Correlation(s, b, length, method...) +} + +func (s *SeriesBase) Rank(length int) SeriesExtend { + return Rank(s, length) }