diff --git a/pkg/bbgo/exit_lower_shadow_take_profit.go b/pkg/bbgo/exit_lower_shadow_take_profit.go index 1ffae6c368..32ac9928e4 100644 --- a/pkg/bbgo/exit_lower_shadow_take_profit.go +++ b/pkg/bbgo/exit_lower_shadow_take_profit.go @@ -48,7 +48,7 @@ func (s *LowerShadowTakeProfit) Bind(session *ExchangeSession, orderExecutor *Ge } // skip close price higher than the ewma - if closePrice.Float64() > ewma.Last() { + if closePrice.Float64() > ewma.Last(0) { return } diff --git a/pkg/bbgo/stop_ema.go b/pkg/bbgo/stop_ema.go index 11c0472b64..f2ec45676c 100644 --- a/pkg/bbgo/stop_ema.go +++ b/pkg/bbgo/stop_ema.go @@ -21,7 +21,7 @@ func (s *StopEMA) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExec } func (s *StopEMA) Allowed(closePrice fixedpoint.Value) bool { - ema := fixedpoint.NewFromFloat(s.stopEWMA.Last()) + ema := fixedpoint.NewFromFloat(s.stopEWMA.Last(0)) if ema.IsZero() { logrus.Infof("stopEMA protection: value is zero, skip") return false diff --git a/pkg/bbgo/trend_ema.go b/pkg/bbgo/trend_ema.go index c37e63aedb..36242fead6 100644 --- a/pkg/bbgo/trend_ema.go +++ b/pkg/bbgo/trend_ema.go @@ -29,12 +29,12 @@ func (s *TrendEMA) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExe } s.last = s.ewma.Values[s.ewma.Length()-2] - s.current = s.ewma.Last() + s.current = s.ewma.Last(0) }) session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, s.Interval, func(kline types.KLine) { s.last = s.current - s.current = s.ewma.Last() + s.current = s.ewma.Last(0) })) } diff --git a/pkg/datatype/floats/slice.go b/pkg/datatype/floats/slice.go index 84e1ff2220..fe9314666b 100644 --- a/pkg/datatype/floats/slice.go +++ b/pkg/datatype/floats/slice.go @@ -183,12 +183,12 @@ func (s Slice) Addr() *Slice { } // Last, Index, Length implements the types.Series interface -func (s Slice) Last() float64 { +func (s Slice) Last(i int) float64 { length := len(s) - if length > 0 { - return s[length-1] + if i < 0 || length-1-i < 0 { + return 0.0 } - return 0.0 + return s[length-1-i] } // Index fetches the element from the end of the slice diff --git a/pkg/indicator/ad.go b/pkg/indicator/ad.go index e94f8d2463..463e304ebb 100644 --- a/pkg/indicator/ad.go +++ b/pkg/indicator/ad.go @@ -35,15 +35,16 @@ func (inc *AD) Update(high, low, cloze, volume float64) { moneyFlowVolume = ((2*cloze - high - low) / (high - low)) * volume } - ad := inc.Last() + moneyFlowVolume + ad := inc.Last(0) + moneyFlowVolume inc.Values.Push(ad) } -func (inc *AD) Last() float64 { - if len(inc.Values) == 0 { - return 0.0 +func (inc *AD) Last(i int) float64 { + length := len(inc.Values) + if length == 0 || length-i-1 < 0 { + return 0 } - return inc.Values[len(inc.Values)-1] + return inc.Values[length-i-1] } func (inc *AD) Index(i int) float64 { @@ -68,7 +69,7 @@ func (inc *AD) CalculateAndUpdate(kLines []types.KLine) { inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64(), k.Volume.Float64()) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) inc.EndTime = kLines[len(kLines)-1].EndTime.Time() } diff --git a/pkg/indicator/alma.go b/pkg/indicator/alma.go index f27de2b704..6837db7669 100644 --- a/pkg/indicator/alma.go +++ b/pkg/indicator/alma.go @@ -20,6 +20,7 @@ import ( // // @param offset: Gaussian applied to the combo line. 1->ema, 0->sma // @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 @@ -64,11 +65,11 @@ func (inc *ALMA) Update(value float64) { } } -func (inc *ALMA) Last() float64 { - if len(inc.Values) == 0 { +func (inc *ALMA) Last(i int) float64 { + if i >= len(inc.Values) { return 0 } - return inc.Values[len(inc.Values)-1] + return inc.Values[len(inc.Values)-i-1] } func (inc *ALMA) Index(i int) float64 { @@ -88,12 +89,12 @@ func (inc *ALMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.input == nil { for _, k := range allKLines { inc.Update(k.Close.Float64()) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } return } inc.Update(allKLines[len(allKLines)-1].Close.Float64()) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func (inc *ALMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { diff --git a/pkg/indicator/alma_test.go b/pkg/indicator/alma_test.go index a00d7113d8..a21342f8a9 100644 --- a/pkg/indicator/alma_test.go +++ b/pkg/indicator/alma_test.go @@ -53,7 +53,7 @@ func Test_ALMA(t *testing.T) { Sigma: 6, } alma.CalculateAndUpdate(tt.kLines) - assert.InDelta(t, tt.want, alma.Last(), Delta) + assert.InDelta(t, tt.want, alma.Last(0), Delta) assert.InDelta(t, tt.next, alma.Index(1), Delta) assert.Equal(t, tt.all, alma.Length()) }) diff --git a/pkg/indicator/atr.go b/pkg/indicator/atr.go index 0d5b264cbf..335c36e2a5 100644 --- a/pkg/indicator/atr.go +++ b/pkg/indicator/atr.go @@ -74,18 +74,18 @@ func (inc *ATR) Update(high, low, cloze float64) { // apply rolling moving average inc.RMA.Update(trueRange) - atr := inc.RMA.Last() + atr := inc.RMA.Last(0) inc.PercentageVolatility.Push(atr / cloze) if len(inc.PercentageVolatility) > MaxNumOfATR { inc.PercentageVolatility = inc.PercentageVolatility[MaxNumOfATRTruncateSize-1:] } } -func (inc *ATR) Last() float64 { +func (inc *ATR) Last(i int) float64 { if inc.RMA == nil { return 0 } - return inc.RMA.Last() + return inc.RMA.Last(i) } func (inc *ATR) Index(i int) float64 { @@ -110,5 +110,5 @@ func (inc *ATR) PushK(k types.KLine) { inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } diff --git a/pkg/indicator/atr_test.go b/pkg/indicator/atr_test.go index b5cb138e98..732dbae22d 100644 --- a/pkg/indicator/atr_test.go +++ b/pkg/indicator/atr_test.go @@ -65,7 +65,7 @@ func Test_calculateATR(t *testing.T) { atr.PushK(k) } - got := atr.Last() + got := atr.Last(0) diff := math.Trunc((got-tt.want)*100) / 100 if diff != 0 { t.Errorf("calculateATR() = %v, want %v", got, tt.want) diff --git a/pkg/indicator/atrp.go b/pkg/indicator/atrp.go index 8d473942ca..4e85589047 100644 --- a/pkg/indicator/atrp.go +++ b/pkg/indicator/atrp.go @@ -21,7 +21,7 @@ import ( // // Calculation: // -// ATRP = (Average True Range / Close) * 100 +// ATRP = (Average True Range / Close) * 100 // //go:generate callbackgen -type ATRP type ATRP struct { @@ -69,15 +69,15 @@ func (inc *ATRP) Update(high, low, cloze float64) { // apply rolling moving average inc.RMA.Update(trueRange) - atr := inc.RMA.Last() + atr := inc.RMA.Last(0) inc.PercentageVolatility.Push(atr / cloze) } -func (inc *ATRP) Last() float64 { +func (inc *ATRP) Last(i int) float64 { if inc.RMA == nil { return 0 } - return inc.RMA.Last() + return inc.RMA.Last(i) } func (inc *ATRP) Index(i int) float64 { @@ -109,7 +109,7 @@ func (inc *ATRP) CalculateAndUpdate(kLines []types.KLine) { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) inc.EndTime = kLines[len(kLines)-1].EndTime.Time() } diff --git a/pkg/indicator/boll.go b/pkg/indicator/boll.go index 712481ffcb..1a670608c1 100644 --- a/pkg/indicator/boll.go +++ b/pkg/indicator/boll.go @@ -84,8 +84,8 @@ func (inc *BOLL) Update(value float64) { inc.SMA.Update(value) inc.StdDev.Update(value) - var sma = inc.SMA.Last() - var stdDev = inc.StdDev.Last() + var sma = inc.SMA.Last(0) + var stdDev = inc.StdDev.Last(0) var band = inc.K * stdDev var upBand = sma + band @@ -105,7 +105,7 @@ func (inc *BOLL) PushK(k types.KLine) { } inc.Update(k.Close.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.SMA.Last(), inc.UpBand.Last(), inc.DownBand.Last()) + inc.EmitUpdate(inc.SMA.Last(0), inc.UpBand.Last(0), inc.DownBand.Last(0)) } func (inc *BOLL) LoadK(allKLines []types.KLine) { @@ -113,7 +113,7 @@ func (inc *BOLL) LoadK(allKLines []types.KLine) { inc.PushK(k) } - inc.EmitUpdate(inc.SMA.Last(), inc.UpBand.Last(), inc.DownBand.Last()) + inc.EmitUpdate(inc.SMA.Last(0), inc.UpBand.Last(0), inc.DownBand.Last(0)) } func (inc *BOLL) CalculateAndUpdate(allKLines []types.KLine) { diff --git a/pkg/indicator/boll_test.go b/pkg/indicator/boll_test.go index 7bc8c574c2..579ce2337a 100644 --- a/pkg/indicator/boll_test.go +++ b/pkg/indicator/boll_test.go @@ -61,8 +61,8 @@ func TestBOLL(t *testing.T) { t.Run(tt.name, func(t *testing.T) { boll := BOLL{IntervalWindow: types.IntervalWindow{Window: tt.window}, K: tt.k} boll.CalculateAndUpdate(tt.kLines) - assert.InDelta(t, tt.up, boll.UpBand.Last(), Delta) - assert.InDelta(t, tt.down, boll.DownBand.Last(), Delta) + assert.InDelta(t, tt.up, boll.UpBand.Last(0), Delta) + assert.InDelta(t, tt.down, boll.DownBand.Last(0), Delta) }) } diff --git a/pkg/indicator/cci.go b/pkg/indicator/cci.go index 64d4427b25..dd24b46c3b 100644 --- a/pkg/indicator/cci.go +++ b/pkg/indicator/cci.go @@ -43,7 +43,7 @@ func (inc *CCI) Update(value float64) { } inc.Input.Push(value) - tp := inc.TypicalPrice.Last() - inc.Input.Index(inc.Window) + value + tp := inc.TypicalPrice.Last(0) - inc.Input.Index(inc.Window) + value inc.TypicalPrice.Push(tp) if len(inc.Input) < inc.Window { return @@ -55,7 +55,7 @@ func (inc *CCI) Update(value float64) { } md := 0. for i := 0; i < inc.Window; i++ { - diff := inc.Input.Index(i) - ma + diff := inc.Input.Last(i) - ma md += diff * diff } md = math.Sqrt(md / float64(inc.Window)) @@ -68,18 +68,12 @@ func (inc *CCI) Update(value float64) { } } -func (inc *CCI) Last() float64 { - if len(inc.Values) == 0 { - return 0 - } - return inc.Values[len(inc.Values)-1] +func (inc *CCI) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *CCI) Index(i int) float64 { - if i >= len(inc.Values) { - return 0 - } - return inc.Values[len(inc.Values)-1-i] + return inc.Last(i) } func (inc *CCI) Length() int { @@ -96,12 +90,12 @@ func (inc *CCI) CalculateAndUpdate(allKLines []types.KLine) { if inc.TypicalPrice.Length() == 0 { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/cci_test.go b/pkg/indicator/cci_test.go index 4aeca6fcab..8d93947ef5 100644 --- a/pkg/indicator/cci_test.go +++ b/pkg/indicator/cci_test.go @@ -29,7 +29,7 @@ func Test_CCI(t *testing.T) { cci.Update(value) } - last := cci.Last() + last := cci.Last(0) assert.InDelta(t, 93.250481, last, Delta) assert.InDelta(t, 81.813449, cci.Index(1), Delta) assert.Equal(t, 50-16+1, cci.Length()) diff --git a/pkg/indicator/cma.go b/pkg/indicator/cma.go index 37dad3f8c7..51043f8054 100644 --- a/pkg/indicator/cma.go +++ b/pkg/indicator/cma.go @@ -7,12 +7,13 @@ import ( // Refer: Cumulative Moving Average, Cumulative Average // Refer: https://en.wikipedia.org/wiki/Moving_average +// //go:generate callbackgen -type CA type CA struct { types.SeriesBase - Interval types.Interval - Values floats.Slice - length float64 + Interval types.Interval + Values floats.Slice + length float64 UpdateCallbacks []func(value float64) } @@ -20,7 +21,7 @@ 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.) + newVal := (inc.Values.Last(0)*inc.length + x) / (inc.length + 1.) inc.length += 1 inc.Values.Push(newVal) if len(inc.Values) > MaxNumOfEWMA { @@ -29,18 +30,12 @@ func (inc *CA) Update(x float64) { } } -func (inc *CA) Last() float64 { - if len(inc.Values) == 0 { - return 0 - } - return inc.Values[len(inc.Values)-1] +func (inc *CA) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *CA) Index(i int) float64 { - if i >= len(inc.Values) { - return 0 - } - return inc.Values[len(inc.Values)-1-i] + return inc.Last(i) } func (inc *CA) Length() int { @@ -56,7 +51,7 @@ func (inc *CA) PushK(k types.KLine) { func (inc *CA) CalculateAndUpdate(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/dema.go b/pkg/indicator/dema.go index 107923de52..6dbc97b595 100644 --- a/pkg/indicator/dema.go +++ b/pkg/indicator/dema.go @@ -51,22 +51,19 @@ func (inc *DEMA) Update(value float64) { } inc.a1.Update(value) - inc.a2.Update(inc.a1.Last()) - inc.Values.Push(2*inc.a1.Last() - inc.a2.Last()) + inc.a2.Update(inc.a1.Last(0)) + inc.Values.Push(2*inc.a1.Last(0) - inc.a2.Last(0)) if len(inc.Values) > MaxNumOfEWMA { inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] } } -func (inc *DEMA) Last() float64 { - return inc.Values.Last() +func (inc *DEMA) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *DEMA) Index(i int) float64 { - if len(inc.Values)-i-1 >= 0 { - return inc.Values[len(inc.Values)-1-i] - } - return 0 + return inc.Last(i) } func (inc *DEMA) Length() int { @@ -83,13 +80,13 @@ func (inc *DEMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.a1 == nil { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } else { // last k k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/dema_test.go b/pkg/indicator/dema_test.go index 99da987b98..845399bbf8 100644 --- a/pkg/indicator/dema_test.go +++ b/pkg/indicator/dema_test.go @@ -46,7 +46,7 @@ func Test_DEMA(t *testing.T) { t.Run(tt.name, func(t *testing.T) { dema := DEMA{IntervalWindow: types.IntervalWindow{Window: 16}} dema.CalculateAndUpdate(tt.kLines) - last := dema.Last() + last := dema.Last(0) assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, dema.Index(1), Delta) assert.Equal(t, tt.all, dema.Length()) diff --git a/pkg/indicator/dmi.go b/pkg/indicator/dmi.go index 853b9deb36..0bf270477a 100644 --- a/pkg/indicator/dmi.go +++ b/pkg/indicator/dmi.go @@ -72,9 +72,9 @@ func (inc *DMI) Update(high, low, cloze float64) { if inc.atr.Length() < inc.Window { return } - k := 100. / inc.atr.Last() - dmp := inc.DMP.Last() - dmn := inc.DMN.Last() + k := 100. / inc.atr.Last(0) + dmp := inc.DMP.Last(0) + dmn := inc.DMN.Last(0) inc.DIPlus.Update(k * dmp) inc.DIMinus.Update(k * dmn) dx := 100. * math.Abs(dmp-dmn) / (dmp + dmn) @@ -108,11 +108,11 @@ func (inc *DMI) CalculateAndUpdate(allKLines []types.KLine) { if inc.ADX == nil { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.DIPlus.Last(), inc.DIMinus.Last(), inc.ADX.Last()) + inc.EmitUpdate(inc.DIPlus.Last(0), inc.DIMinus.Last(0), inc.ADX.Last(0)) } } else { inc.PushK(last) - inc.EmitUpdate(inc.DIPlus.Last(), inc.DIMinus.Last(), inc.ADX.Last()) + inc.EmitUpdate(inc.DIPlus.Last(0), inc.DIMinus.Last(0), inc.ADX.Last(0)) } } diff --git a/pkg/indicator/dmi_test.go b/pkg/indicator/dmi_test.go index d93e164409..dae952d52b 100644 --- a/pkg/indicator/dmi_test.go +++ b/pkg/indicator/dmi_test.go @@ -78,9 +78,9 @@ func Test_DMI(t *testing.T) { ADXSmoothing: 14, } dmi.CalculateAndUpdate(tt.klines) - assert.InDelta(t, dmi.GetDIPlus().Last(), tt.want.dip, Delta) - assert.InDelta(t, dmi.GetDIMinus().Last(), tt.want.dim, Delta) - assert.InDelta(t, dmi.GetADX().Last(), tt.want.adx, Delta) + assert.InDelta(t, dmi.GetDIPlus().Last(0), tt.want.dip, Delta) + assert.InDelta(t, dmi.GetDIMinus().Last(0), tt.want.dim, Delta) + assert.InDelta(t, dmi.GetADX().Last(0), tt.want.adx, Delta) }) } diff --git a/pkg/indicator/drift.go b/pkg/indicator/drift.go index ac3df837aa..eb1969720f 100644 --- a/pkg/indicator/drift.go +++ b/pkg/indicator/drift.go @@ -51,7 +51,7 @@ func (inc *Drift) Update(value float64) { inc.chng.Update(chng) if inc.chng.Length() >= inc.Window { stdev := types.Stdev(inc.chng, inc.Window) - drift := inc.MA.Last() - stdev*stdev*0.5 + drift := inc.MA.Last(0) - stdev*stdev*0.5 inc.Values.Push(drift) } } @@ -74,7 +74,7 @@ func (inc *Drift) ZeroPoint() float64 { } else { return N2 }*/ - return inc.LastValue * math.Exp(window*(0.5*stdev*stdev)+chng-inc.MA.Last()*window) + return inc.LastValue * math.Exp(window*(0.5*stdev*stdev)+chng-inc.MA.Last(0)*window) } func (inc *Drift) Clone() (out *Drift) { @@ -96,17 +96,11 @@ func (inc *Drift) TestUpdate(value float64) *Drift { } func (inc *Drift) Index(i int) float64 { - if inc.Values == nil { - return 0 - } - return inc.Values.Index(i) + return inc.Last(i) } -func (inc *Drift) Last() float64 { - if inc.Values.Length() == 0 { - return 0 - } - return inc.Values.Last() +func (inc *Drift) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *Drift) Length() int { @@ -126,12 +120,12 @@ func (inc *Drift) CalculateAndUpdate(allKLines []types.KLine) { if inc.chng == nil { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/emv.go b/pkg/indicator/emv.go index a3a761a5a1..a391a08e70 100644 --- a/pkg/indicator/emv.go +++ b/pkg/indicator/emv.go @@ -55,11 +55,12 @@ func (inc *EMV) Index(i int) float64 { return inc.Values.Index(i) } -func (inc *EMV) Last() float64 { +func (inc *EMV) Last(i int) float64 { if inc.Values == nil { return 0 } - return inc.Values.Last() + + return inc.Values.Last(i) } func (inc *EMV) Length() int { diff --git a/pkg/indicator/emv_test.go b/pkg/indicator/emv_test.go index 03b57a7604..0be0b517dc 100644 --- a/pkg/indicator/emv_test.go +++ b/pkg/indicator/emv_test.go @@ -16,7 +16,7 @@ func Test_EMV(t *testing.T) { } emv.Update(63.74, 62.63, 32178836) emv.Update(64.51, 63.85, 36461672) - assert.InDelta(t, 1.8, emv.Values.rawValues.Last(), Delta) + assert.InDelta(t, 1.8, emv.Values.rawValues.Last(0), Delta) emv.Update(64.57, 63.81, 51372680) emv.Update(64.31, 62.62, 42476356) emv.Update(63.43, 62.73, 29504176) @@ -30,5 +30,5 @@ func Test_EMV(t *testing.T) { emv.Update(65.25, 64.48, 37015388) emv.Update(64.69, 63.65, 40672116) emv.Update(64.26, 63.68, 35627200) - assert.InDelta(t, -0.03, emv.Last(), Delta) + assert.InDelta(t, -0.03, emv.Last(0), Delta) } diff --git a/pkg/indicator/ewma.go b/pkg/indicator/ewma.go index 533c27bd28..e05f30a3cb 100644 --- a/pkg/indicator/ewma.go +++ b/pkg/indicator/ewma.go @@ -50,24 +50,20 @@ func (inc *EWMA) Update(value float64) { inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] } - ema := (1-multiplier)*inc.Last() + multiplier*value + ema := (1-multiplier)*inc.Last(0) + multiplier*value inc.Values.Push(ema) } -func (inc *EWMA) Last() float64 { +func (inc *EWMA) Last(i int) float64 { if len(inc.Values) == 0 { return 0 } - return inc.Values[len(inc.Values)-1] + return inc.Values.Last(i) } func (inc *EWMA) Index(i int) float64 { - if i >= len(inc.Values) { - return 0 - } - - return inc.Values[len(inc.Values)-1-i] + return inc.Last(i) } func (inc *EWMA) Length() int { @@ -81,7 +77,7 @@ func (inc *EWMA) PushK(k types.KLine) { inc.Update(k.Close.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func CalculateKLinesEMA(allKLines []types.KLine, priceF KLineValueMapper, window int) float64 { diff --git a/pkg/indicator/fisher.go b/pkg/indicator/fisher.go index fcd492c955..98198fc045 100644 --- a/pkg/indicator/fisher.go +++ b/pkg/indicator/fisher.go @@ -16,6 +16,7 @@ import ( // The Fisher Transform is calculated by taking the natural logarithm of the ratio of the security's current price to its moving average, // and then double-smoothing the result. This resulting line is called the Fisher Transform line, and can be plotted on the price chart // along with the security's price. +// //go:generate callbackgen -type FisherTransform type FisherTransform struct { types.SeriesBase @@ -60,18 +61,15 @@ func (inc *FisherTransform) Update(value float64) { } } -func (inc *FisherTransform) Last() float64 { +func (inc *FisherTransform) Last(i int) float64 { if inc.Values == nil { return 0.0 } - return inc.Values.Last() + return inc.Values.Last(i) } func (inc *FisherTransform) Index(i int) float64 { - if inc.Values == nil { - return 0.0 - } - return inc.Values.Index(i) + return inc.Last(i) } func (inc *FisherTransform) Length() int { diff --git a/pkg/indicator/float64updater.go b/pkg/indicator/float64updater.go index 3c3746ffef..6f4e69ddb9 100644 --- a/pkg/indicator/float64updater.go +++ b/pkg/indicator/float64updater.go @@ -23,16 +23,12 @@ func NewFloat64Series(v ...float64) Float64Series { return s } -func (f *Float64Series) Last() float64 { - return f.slice.Last() +func (f *Float64Series) Last(i int) float64 { + return f.slice.Last(i) } func (f *Float64Series) Index(i int) float64 { - length := len(f.slice) - if length == 0 || length-i-1 < 0 { - return 0 - } - return f.slice[length-i-1] + return f.slice.Last(i) } func (f *Float64Series) Length() int { diff --git a/pkg/indicator/ghfilter.go b/pkg/indicator/ghfilter.go index f5794d4d59..e5dea876f2 100644 --- a/pkg/indicator/ghfilter.go +++ b/pkg/indicator/ghfilter.go @@ -1,9 +1,10 @@ package indicator import ( + "math" + "github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/types" - "math" ) // Refer: https://jamesgoulding.com/Research_II/Ehlers/Ehlers%20(Optimal%20Tracking%20Filters).doc @@ -39,16 +40,13 @@ func (inc *GHFilter) update(value, uncertainty float64) { lambda := inc.a / inc.b lambda2 := lambda * lambda alpha := (-lambda2 + math.Sqrt(lambda2*lambda2+16*lambda2)) / 8 - filtered := alpha*value + (1-alpha)*inc.Values.Last() + filtered := alpha*value + (1-alpha)*inc.Values.Last(0) inc.Values.Push(filtered) inc.lastMeasurement = value } func (inc *GHFilter) Index(i int) float64 { - if inc.Values == nil { - return 0.0 - } - return inc.Values.Index(i) + return inc.Last(i) } func (inc *GHFilter) Length() int { @@ -58,11 +56,11 @@ func (inc *GHFilter) Length() int { return inc.Values.Length() } -func (inc *GHFilter) Last() float64 { +func (inc *GHFilter) Last(i int) float64 { if inc.Values == nil { return 0.0 } - return inc.Values.Last() + return inc.Values.Last(i) } // interfaces implementation check diff --git a/pkg/indicator/ghfilter_test.go b/pkg/indicator/ghfilter_test.go index ac74399335..8494388590 100644 --- a/pkg/indicator/ghfilter_test.go +++ b/pkg/indicator/ghfilter_test.go @@ -6058,7 +6058,7 @@ func Test_GHFilter(t *testing.T) { for _, k := range klines { filter.PushK(k) } - got := filter.Last() + got := filter.Last(0) got = math.Trunc(got*100.0) / 100.0 if got != tt.want { t.Errorf("GHFilter.Last() = %v, want %v", got, tt.want) @@ -6125,10 +6125,10 @@ func Test_GHFilterEstimationAccurate(t *testing.T) { for i, k := range klines { // square error between last estimated state and current actual state if i > 0 { - filterDiff2Sum += klineSquareError(filter.Last(), k) - ewmaDiff2Sum += klineSquareError(ewma.Last(), k) - filterCloseDiff2Sum += closeSquareError(filter.Last(), k) - ewmaCloseDiff2Sum += closeSquareError(ewma.Last(), k) + filterDiff2Sum += klineSquareError(filter.Last(0), k) + ewmaDiff2Sum += klineSquareError(ewma.Last(0), k) + filterCloseDiff2Sum += closeSquareError(filter.Last(0), k) + ewmaCloseDiff2Sum += closeSquareError(ewma.Last(0), k) } // update estimations diff --git a/pkg/indicator/gma.go b/pkg/indicator/gma.go index 8b86733002..1fe0abb52b 100644 --- a/pkg/indicator/gma.go +++ b/pkg/indicator/gma.go @@ -22,18 +22,15 @@ type GMA struct { UpdateCallbacks []func(value float64) } -func (inc *GMA) Last() float64 { +func (inc *GMA) Last(i int) float64 { if inc.SMA == nil { return 0.0 } - return math.Exp(inc.SMA.Last()) + return math.Exp(inc.SMA.Last(i)) } func (inc *GMA) Index(i int) float64 { - if inc.SMA == nil { - return 0.0 - } - return math.Exp(inc.SMA.Index(i)) + return inc.Last(i) } func (inc *GMA) Length() int { diff --git a/pkg/indicator/gma_test.go b/pkg/indicator/gma_test.go index ab9740e094..0ad27f9776 100644 --- a/pkg/indicator/gma_test.go +++ b/pkg/indicator/gma_test.go @@ -51,10 +51,10 @@ func Test_GMA(t *testing.T) { for _, k := range tt.kLines { gma.PushK(k) } - assert.InDelta(t, tt.want, gma.Last(), Delta) + assert.InDelta(t, tt.want, gma.Last(0), Delta) assert.InDelta(t, tt.next, gma.Index(1), Delta) gma.Update(tt.update) - assert.InDelta(t, tt.updateResult, gma.Last(), Delta) + assert.InDelta(t, tt.updateResult, gma.Last(0), Delta) assert.Equal(t, tt.all, gma.Length()) }) } diff --git a/pkg/indicator/hull.go b/pkg/indicator/hull.go index 214d2620d3..7a432d39af 100644 --- a/pkg/indicator/hull.go +++ b/pkg/indicator/hull.go @@ -14,6 +14,7 @@ import ( // the weighted moving average of the input data using a weighting factor of W, where W is the square root of the length of the moving average. // The result is then double-smoothed by taking the weighted moving average of this result using a weighting factor of W/2. This final average // forms the HMA line, which can be used to make predictions about future price movements. +// //go:generate callbackgen -type HULL type HULL struct { types.SeriesBase @@ -36,14 +37,14 @@ func (inc *HULL) Update(value float64) { } inc.ma1.Update(value) inc.ma2.Update(value) - inc.result.Update(2*inc.ma1.Last() - inc.ma2.Last()) + inc.result.Update(2*inc.ma1.Last(0) - inc.ma2.Last(0)) } -func (inc *HULL) Last() float64 { +func (inc *HULL) Last(i int) float64 { if inc.result == nil { return 0 } - return inc.result.Last() + return inc.result.Index(i) } func (inc *HULL) Index(i int) float64 { @@ -66,5 +67,5 @@ func (inc *HULL) PushK(k types.KLine) { } inc.Update(k.Close.Float64()) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } diff --git a/pkg/indicator/hull_test.go b/pkg/indicator/hull_test.go index 857c8d30da..e1c077a739 100644 --- a/pkg/indicator/hull_test.go +++ b/pkg/indicator/hull_test.go @@ -51,7 +51,7 @@ func Test_HULL(t *testing.T) { hull.PushK(k) } - last := hull.Last() + last := hull.Last(0) assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, hull.Index(1), Delta) assert.Equal(t, tt.all, hull.Length()) diff --git a/pkg/indicator/interface.go b/pkg/indicator/interface.go index d784f53087..51307440b3 100644 --- a/pkg/indicator/interface.go +++ b/pkg/indicator/interface.go @@ -25,7 +25,7 @@ type KLinePusher interface { // Simple is the simple indicator that only returns one float64 value type Simple interface { KLinePusher - Last() float64 + Last(int) float64 OnUpdate(f func(value float64)) } diff --git a/pkg/indicator/kalmanfilter.go b/pkg/indicator/kalmanfilter.go index 5d83457290..0b9ce2b0a0 100644 --- a/pkg/indicator/kalmanfilter.go +++ b/pkg/indicator/kalmanfilter.go @@ -1,9 +1,10 @@ package indicator import ( + "math" + "github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/types" - "math" ) // Refer: https://www.kalmanfilter.net/kalman1d.html @@ -25,7 +26,7 @@ type KalmanFilter struct { func (inc *KalmanFilter) Update(value float64) { var measureMove = value if inc.measurements != nil { - measureMove = value - inc.measurements.Last() + measureMove = value - inc.measurements.Last(0) } inc.update(value, math.Abs(measureMove)) } @@ -46,7 +47,7 @@ func (inc *KalmanFilter) update(value, amp float64) { q := math.Sqrt(types.Mean(inc.amp2)) * float64(1+inc.AdditionalSmoothWindow) // update - lastPredict := inc.Values.Last() + lastPredict := inc.Values.Last(0) curState := value + (value - lastPredict) estimated := lastPredict + inc.k*(curState-lastPredict) @@ -57,24 +58,15 @@ func (inc *KalmanFilter) update(value, amp float64) { } func (inc *KalmanFilter) Index(i int) float64 { - if inc.Values == nil { - return 0.0 - } - return inc.Values.Index(i) + return inc.Last(i) } func (inc *KalmanFilter) Length() int { - if inc.Values == nil { - return 0 - } return inc.Values.Length() } -func (inc *KalmanFilter) Last() float64 { - if inc.Values == nil { - return 0.0 - } - return inc.Values.Last() +func (inc *KalmanFilter) Last(i int) float64 { + return inc.Values.Last(i) } // interfaces implementation check diff --git a/pkg/indicator/kalmanfilter_test.go b/pkg/indicator/kalmanfilter_test.go index 250656047e..c357748e4f 100644 --- a/pkg/indicator/kalmanfilter_test.go +++ b/pkg/indicator/kalmanfilter_test.go @@ -6065,7 +6065,7 @@ func Test_KalmanFilter(t *testing.T) { for _, k := range klines { filter.PushK(k) } - got := filter.Last() + got := filter.Last(0) got = math.Trunc(got*100.0) / 100.0 if got != tt.want { t.Errorf("KalmanFilter.Last() = %v, want %v", got, tt.want) @@ -6160,10 +6160,10 @@ func Test_KalmanFilterEstimationAccurate(t *testing.T) { for _, k := range klines { // square error between last estimated state and current actual state if ewma.Length() > 0 { - filterDiff2Sum += klineSquareError(filter.Last(), k) - ewmaDiff2Sum += klineSquareError(ewma.Last(), k) - filterCloseDiff2Sum += closeSquareError(filter.Last(), k) - ewmaCloseDiff2Sum += closeSquareError(ewma.Last(), k) + filterDiff2Sum += klineSquareError(filter.Last(0), k) + ewmaDiff2Sum += klineSquareError(ewma.Last(0), k) + filterCloseDiff2Sum += closeSquareError(filter.Last(0), k) + ewmaCloseDiff2Sum += closeSquareError(ewma.Last(0), k) numEstimations++ } diff --git a/pkg/indicator/klingeroscillator.go b/pkg/indicator/klingeroscillator.go index 198a5d29d7..0b5f703108 100644 --- a/pkg/indicator/klingeroscillator.go +++ b/pkg/indicator/klingeroscillator.go @@ -12,6 +12,7 @@ import ( // The Klinger Oscillator is calculated by taking the difference between a 34-period and 55-period moving average. // Usually the indicator is using together with a 9-period or 13-period of moving average as the signal line. // This indicator is often used to identify potential turning points in the market, as well as to confirm the strength of a trend. +// //go:generate callbackgen -type KlingerOscillator type KlingerOscillator struct { types.SeriesBase @@ -30,17 +31,11 @@ func (inc *KlingerOscillator) Length() int { return inc.Fast.Length() } -func (inc *KlingerOscillator) Last() float64 { +func (inc *KlingerOscillator) Last(i int) float64 { if inc.Fast == nil || inc.Slow == nil { return 0 } - return inc.Fast.Last() - inc.Slow.Last() -} -func (inc *KlingerOscillator) Index(i int) float64 { - if inc.Fast == nil || inc.Slow == nil { - return 0 - } - return inc.Fast.Index(i) - inc.Slow.Index(i) + return inc.Fast.Last(i) - inc.Slow.Last(i) } func (inc *KlingerOscillator) Update(high, low, cloze, volume float64) { diff --git a/pkg/indicator/line.go b/pkg/indicator/line.go index edb8276f16..e6227b30f8 100644 --- a/pkg/indicator/line.go +++ b/pkg/indicator/line.go @@ -38,12 +38,12 @@ func (l *Line) Bind(updater KLineWindowUpdater) { updater.OnKLineWindowUpdate(l.handleKLineWindowUpdate) } -func (l *Line) Last() float64 { - return (l.end-l.start)/float64(l.startIndex-l.endIndex)*float64(l.endIndex) + l.end +func (l *Line) Last(i int) float64 { + return (l.end-l.start)/float64(l.startIndex-l.endIndex)*float64(l.endIndex-i) + l.end } func (l *Line) Index(i int) float64 { - return (l.end-l.start)/float64(l.startIndex-l.endIndex)*float64(l.endIndex-i) + l.end + return l.Last(i) } func (l *Line) Length() int { diff --git a/pkg/indicator/linreg.go b/pkg/indicator/linreg.go index 3bb4606edc..f27e32c2a5 100644 --- a/pkg/indicator/linreg.go +++ b/pkg/indicator/linreg.go @@ -1,9 +1,10 @@ package indicator import ( - "github.com/sirupsen/logrus" "time" + "github.com/sirupsen/logrus" + "github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/types" ) @@ -11,6 +12,7 @@ import ( var logLinReg = logrus.WithField("indicator", "LinReg") // LinReg is Linear Regression baseline +// //go:generate callbackgen -type LinReg type LinReg struct { types.SeriesBase @@ -28,11 +30,8 @@ type LinReg struct { } // Last slope of linear regression baseline -func (lr *LinReg) Last() float64 { - if lr.Values.Length() == 0 { - return 0.0 - } - return lr.Values.Last() +func (lr *LinReg) Last(i int) float64 { + return lr.Values.Last(i) } // LastRatio of slope to price @@ -40,16 +39,12 @@ func (lr *LinReg) LastRatio() float64 { if lr.ValueRatios.Length() == 0 { return 0.0 } - return lr.ValueRatios.Last() + return lr.ValueRatios.Last(0) } // 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) + return lr.Values.Last(i) } // IndexRatio returns the slope ratio @@ -58,7 +53,7 @@ func (lr *LinReg) IndexRatio(i int) float64 { return 0.0 } - return lr.ValueRatios.Index(i) + return lr.ValueRatios.Last(i) } // Length of the slope values @@ -99,9 +94,9 @@ func (lr *LinReg) Update(kline types.KLine) { endPrice := average - slope*sumX/length + slope startPrice := endPrice + slope*(length-1) lr.Values.Push((endPrice - startPrice) / (length - 1)) - lr.ValueRatios.Push(lr.Values.Last() / kline.GetClose().Float64()) + lr.ValueRatios.Push(lr.Values.Last(0) / kline.GetClose().Float64()) - logLinReg.Debugf("linear regression baseline slope: %f", lr.Last()) + logLinReg.Debugf("linear regression baseline slope: %f", lr.Last(0)) } func (lr *LinReg) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) { diff --git a/pkg/indicator/low.go b/pkg/indicator/low.go index 77d0457ead..6f6d9468b4 100644 --- a/pkg/indicator/low.go +++ b/pkg/indicator/low.go @@ -33,5 +33,5 @@ func (inc *Low) PushK(k types.KLine) { inc.Update(k.Low.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } diff --git a/pkg/indicator/macd.go b/pkg/indicator/macd.go index 19818c70e8..2535e96814 100644 --- a/pkg/indicator/macd.go +++ b/pkg/indicator/macd.go @@ -59,14 +59,14 @@ func (inc *MACDLegacy) Update(x float64) { inc.slowEWMA.Update(x) // update MACD value, it's also the signal line - fast := inc.fastEWMA.Last() - slow := inc.slowEWMA.Last() + fast := inc.fastEWMA.Last(0) + slow := inc.slowEWMA.Last(0) macd := fast - slow inc.Values.Push(macd) // update signal line inc.signalLine.Update(macd) - signal := inc.signalLine.Last() + signal := inc.signalLine.Last(0) // update histogram histogram := macd - signal @@ -75,7 +75,7 @@ func (inc *MACDLegacy) Update(x float64) { inc.EmitUpdate(macd, signal, histogram) } -func (inc *MACDLegacy) Last() float64 { +func (inc *MACDLegacy) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } @@ -106,21 +106,12 @@ type MACDValues struct { *MACDLegacy } -func (inc *MACDValues) Last() float64 { - if len(inc.Values) == 0 { - return 0.0 - } - - return inc.Values[len(inc.Values)-1] +func (inc *MACDValues) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *MACDValues) Index(i int) float64 { - length := len(inc.Values) - if length == 0 || length-1-i < 0 { - return 0.0 - } - - return inc.Values[length-1+i] + return inc.Values.Last(i) } func (inc *MACDValues) Length() int { diff --git a/pkg/indicator/macd_test.go b/pkg/indicator/macd_test.go index c9bede48ad..eac9d54d9c 100644 --- a/pkg/indicator/macd_test.go +++ b/pkg/indicator/macd_test.go @@ -45,7 +45,7 @@ func Test_calculateMACD(t *testing.T) { macd.PushK(k) } - got := macd.Last() + got := macd.Last(0) diff := math.Trunc((got-tt.want)*100) / 100 if diff != 0 { t.Errorf("calculateMACD() = %v, want %v", got, tt.want) diff --git a/pkg/indicator/obv.go b/pkg/indicator/obv.go index 4ca032445d..f7b768ce3b 100644 --- a/pkg/indicator/obv.go +++ b/pkg/indicator/obv.go @@ -40,24 +40,18 @@ func (inc *OBV) Update(price, volume float64) { } if volume < inc.PrePrice { - inc.Values.Push(inc.Last() - volume) + inc.Values.Push(inc.Last(0) - volume) } else { - inc.Values.Push(inc.Last() + volume) + inc.Values.Push(inc.Last(0) + volume) } } -func (inc *OBV) Last() float64 { - if len(inc.Values) == 0 { - return 0.0 - } - return inc.Values[len(inc.Values)-1] +func (inc *OBV) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *OBV) Index(i int) float64 { - if len(inc.Values)-i <= 0 { - return 0.0 - } - return inc.Values[len(inc.Values)-i-1] + return inc.Last(i) } var _ types.SeriesExtend = &OBV{} @@ -75,7 +69,7 @@ func (inc *OBV) CalculateAndUpdate(kLines []types.KLine) { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) inc.EndTime = kLines[len(kLines)-1].EndTime.Time() } diff --git a/pkg/indicator/pivot.go b/pkg/indicator/pivot.go index 8e027e4109..c32db25cd6 100644 --- a/pkg/indicator/pivot.go +++ b/pkg/indicator/pivot.go @@ -10,7 +10,6 @@ import ( "github.com/c9s/bbgo/pkg/types" ) - //go:generate callbackgen -type Pivot type Pivot struct { types.IntervalWindow @@ -105,12 +104,12 @@ func calculatePivot(klines []types.KLine, window int, valLow KLineValueMapper, v } pl := 0. - if lows.Min() == lows.Index(int(window/2.)-1) { + if lows.Min() == lows.Last(int(window/2.)-1) { pl = lows.Min() } ph := 0. - if highs.Max() == highs.Index(int(window/2.)-1) { + if highs.Max() == highs.Last(int(window/2.)-1) { ph = highs.Max() } diff --git a/pkg/indicator/pivothigh.go b/pkg/indicator/pivothigh.go index 8414c826cd..52fe7be0b4 100644 --- a/pkg/indicator/pivothigh.go +++ b/pkg/indicator/pivothigh.go @@ -24,12 +24,12 @@ func (inc *PivotHigh) Length() int { return inc.Values.Length() } -func (inc *PivotHigh) Last() float64 { +func (inc *PivotHigh) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } - return inc.Values.Last() + return inc.Values.Last(0) } func (inc *PivotHigh) Update(value float64) { @@ -60,5 +60,5 @@ func (inc *PivotHigh) PushK(k types.KLine) { inc.Update(k.High.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } diff --git a/pkg/indicator/pivotlow.go b/pkg/indicator/pivotlow.go index c195c488c8..7bdbd58e67 100644 --- a/pkg/indicator/pivotlow.go +++ b/pkg/indicator/pivotlow.go @@ -24,12 +24,12 @@ func (inc *PivotLow) Length() int { return inc.Values.Length() } -func (inc *PivotLow) Last() float64 { +func (inc *PivotLow) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } - return inc.Values.Last() + return inc.Values.Last(0) } func (inc *PivotLow) Update(value float64) { @@ -60,7 +60,7 @@ func (inc *PivotLow) PushK(k types.KLine) { inc.Update(k.Low.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func calculatePivotHigh(highs floats.Slice, left, right int) (float64, bool) { diff --git a/pkg/indicator/psar.go b/pkg/indicator/psar.go index e39e3fb2ce..5154c1b547 100644 --- a/pkg/indicator/psar.go +++ b/pkg/indicator/psar.go @@ -34,11 +34,11 @@ type PSAR struct { UpdateCallbacks []func(value float64) } -func (inc *PSAR) Last() float64 { +func (inc *PSAR) Last(int) float64 { if len(inc.Values) == 0 { return 0 } - return inc.Values.Last() + return inc.Values.Last(0) } func (inc *PSAR) Length() int { @@ -46,8 +46,8 @@ func (inc *PSAR) Length() int { } func (inc *PSAR) falling() bool { - up := inc.High.Last() - inc.High.Index(1) - dn := inc.Low.Index(1) - inc.Low.Last() + up := inc.High.Last(0) - inc.High.Index(1) + dn := inc.Low.Index(1) - inc.Low.Last(0) return (dn > up) && (dn > 0) } @@ -66,7 +66,7 @@ func (inc *PSAR) Update(high, low float64) { inc.High.Update(high) inc.Low.Update(low) if !isFirst { - ppsar := inc.Values.Last() + ppsar := inc.Values.Last(0) if inc.Falling { // falling formula psar := ppsar - inc.AF*(ppsar-inc.EP) h := inc.High.Shift(1).Highest(2) diff --git a/pkg/indicator/psar_test.go b/pkg/indicator/psar_test.go index 6914f7e547..a791a28cc5 100644 --- a/pkg/indicator/psar_test.go +++ b/pkg/indicator/psar_test.go @@ -36,5 +36,5 @@ func Test_PSAR(t *testing.T) { } assert.Equal(t, psar.Length(), 29) assert.Equal(t, psar.AF, 0.04) - assert.Equal(t, psar.Last(), 0.16) + assert.Equal(t, psar.Last(0), 0.16) } diff --git a/pkg/indicator/rma.go b/pkg/indicator/rma.go index 6785a54ec4..a06ad18af9 100644 --- a/pkg/indicator/rma.go +++ b/pkg/indicator/rma.go @@ -78,16 +78,12 @@ func (inc *RMA) Update(x float64) { } } -func (inc *RMA) Last() float64 { - return inc.Values.Last() +func (inc *RMA) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *RMA) Index(i int) float64 { - length := len(inc.Values) - if length == 0 || length-i-1 < 0 { - return 0 - } - return inc.Values[length-i-1] + return inc.Last(i) } func (inc *RMA) Length() int { @@ -116,7 +112,7 @@ func (inc *RMA) CalculateAndUpdate(kLines []types.KLine) { inc.PushK(last) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func (inc *RMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { diff --git a/pkg/indicator/rsi.go b/pkg/indicator/rsi.go index 8689abbdf6..acae219c34 100644 --- a/pkg/indicator/rsi.go +++ b/pkg/indicator/rsi.go @@ -65,19 +65,12 @@ func (inc *RSI) Update(price float64) { inc.PreviousAvgLoss = avgLoss } -func (inc *RSI) Last() float64 { - if len(inc.Values) == 0 { - return 0.0 - } - return inc.Values[len(inc.Values)-1] +func (inc *RSI) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *RSI) Index(i int) float64 { - length := len(inc.Values) - if length <= 0 || length-i-1 < 0 { - return 0.0 - } - return inc.Values[length-i-1] + return inc.Last(i) } func (inc *RSI) Length() int { @@ -99,7 +92,7 @@ func (inc *RSI) CalculateAndUpdate(kLines []types.KLine) { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) inc.EndTime = kLines[len(kLines)-1].EndTime.Time() } diff --git a/pkg/indicator/sma.go b/pkg/indicator/sma.go index 508a0ce7eb..cc3bb1bc70 100644 --- a/pkg/indicator/sma.go +++ b/pkg/indicator/sma.go @@ -22,19 +22,12 @@ type SMA struct { UpdateCallbacks []func(value float64) } -func (inc *SMA) Last() float64 { - if inc.Values.Length() == 0 { - return 0.0 - } - return inc.Values.Last() +func (inc *SMA) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *SMA) Index(i int) float64 { - if i >= inc.Values.Length() { - return 0.0 - } - - return inc.Values.Index(i) + return inc.Last(i) } func (inc *SMA) Length() int { @@ -81,7 +74,7 @@ func (inc *SMA) PushK(k types.KLine) { inc.Update(k.Close.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Values.Last()) + inc.EmitUpdate(inc.Values.Last(0)) } func (inc *SMA) LoadK(allKLines []types.KLine) { diff --git a/pkg/indicator/sma_test.go b/pkg/indicator/sma_test.go index a6ecc13241..c577af2153 100644 --- a/pkg/indicator/sma_test.go +++ b/pkg/indicator/sma_test.go @@ -58,10 +58,10 @@ func Test_SMA(t *testing.T) { sma.PushK(k) } - assert.InDelta(t, tt.want, sma.Last(), Delta) + assert.InDelta(t, tt.want, sma.Last(0), Delta) assert.InDelta(t, tt.next, sma.Index(1), Delta) sma.Update(tt.update) - assert.InDelta(t, tt.updateResult, sma.Last(), Delta) + assert.InDelta(t, tt.updateResult, sma.Last(0), Delta) assert.Equal(t, tt.all, sma.Length()) }) } diff --git a/pkg/indicator/ssf.go b/pkg/indicator/ssf.go index 9458e16e86..3a0f71bb35 100644 --- a/pkg/indicator/ssf.go +++ b/pkg/indicator/ssf.go @@ -50,9 +50,9 @@ func (inc *SSF) Update(value float64) { } result := inc.c1*value + - inc.c2*inc.Values.Index(0) + - inc.c3*inc.Values.Index(1) + - inc.c4*inc.Values.Index(2) + inc.c2*inc.Values.Last(0) + + inc.c3*inc.Values.Last(1) + + inc.c4*inc.Values.Last(2) inc.Values.Push(result) } else { // poles == 2 if inc.Values == nil { @@ -65,17 +65,18 @@ func (inc *SSF) Update(value float64) { inc.Values = floats.Slice{} } result := inc.c1*value + - inc.c2*inc.Values.Index(0) + - inc.c3*inc.Values.Index(1) + inc.c2*inc.Values.Last(0) + + inc.c3*inc.Values.Last(1) inc.Values.Push(result) } } +func (inc *SSF) Last(i int) float64 { + return inc.Values.Last(i) +} + func (inc *SSF) Index(i int) float64 { - if inc.Values == nil { - return 0.0 - } - return inc.Values.Index(i) + return inc.Last(i) } func (inc *SSF) Length() int { @@ -85,13 +86,6 @@ func (inc *SSF) Length() int { return inc.Values.Length() } -func (inc *SSF) Last() float64 { - if inc.Values == nil { - return 0.0 - } - return inc.Values.Last() -} - var _ types.SeriesExtend = &SSF{} func (inc *SSF) PushK(k types.KLine) { @@ -102,12 +96,12 @@ func (inc *SSF) CalculateAndUpdate(allKLines []types.KLine) { if inc.Values != nil { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) return } for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/ssf_test.go b/pkg/indicator/ssf_test.go index 253d722204..49f10c30f8 100644 --- a/pkg/indicator/ssf_test.go +++ b/pkg/indicator/ssf_test.go @@ -63,7 +63,7 @@ func Test_SSF(t *testing.T) { Poles: tt.poles, } ssf.CalculateAndUpdate(tt.kLines) - assert.InDelta(t, tt.want, ssf.Last(), Delta) + assert.InDelta(t, tt.want, ssf.Last(0), Delta) assert.InDelta(t, tt.next, ssf.Index(1), Delta) assert.Equal(t, tt.all, ssf.Length()) }) diff --git a/pkg/indicator/stddev.go b/pkg/indicator/stddev.go index a63aa89ef2..7e06c448c4 100644 --- a/pkg/indicator/stddev.go +++ b/pkg/indicator/stddev.go @@ -21,19 +21,12 @@ type StdDev struct { updateCallbacks []func(value float64) } -func (inc *StdDev) Last() float64 { - if inc.Values.Length() == 0 { - return 0.0 - } - return inc.Values.Last() +func (inc *StdDev) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *StdDev) Index(i int) float64 { - if i >= inc.Values.Length() { - return 0.0 - } - - return inc.Values.Index(i) + return inc.Last(i) } func (inc *StdDev) Length() int { @@ -76,7 +69,7 @@ func (inc *StdDev) CalculateAndUpdate(allKLines []types.KLine) { inc.PushK(last) } - inc.EmitUpdate(inc.Values.Last()) + inc.EmitUpdate(inc.Values.Last(0)) } func (inc *StdDev) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { diff --git a/pkg/indicator/supertrend.go b/pkg/indicator/supertrend.go index 4ab050dea8..bd891dd446 100644 --- a/pkg/indicator/supertrend.go +++ b/pkg/indicator/supertrend.go @@ -50,16 +50,12 @@ type Supertrend struct { UpdateCallbacks []func(value float64) } -func (inc *Supertrend) Last() float64 { - return inc.trendPrices.Last() +func (inc *Supertrend) Last(i int) float64 { + return inc.trendPrices.Last(i) } func (inc *Supertrend) Index(i int) float64 { - length := inc.Length() - if length == 0 || length-i-1 < 0 { - return 0 - } - return inc.trendPrices[length-i-1] + return inc.Last(i) } func (inc *Supertrend) Length() int { @@ -94,13 +90,13 @@ func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) { src := (highPrice + lowPrice) / 2 // Update uptrend - inc.uptrendPrice = src - inc.AverageTrueRange.Last()*inc.ATRMultiplier + inc.uptrendPrice = src - inc.AverageTrueRange.Last(0)*inc.ATRMultiplier if inc.previousClosePrice > inc.previousUptrendPrice { inc.uptrendPrice = math.Max(inc.uptrendPrice, inc.previousUptrendPrice) } // Update downtrend - inc.downtrendPrice = src + inc.AverageTrueRange.Last()*inc.ATRMultiplier + inc.downtrendPrice = src + inc.AverageTrueRange.Last(0)*inc.ATRMultiplier if inc.previousClosePrice < inc.previousDowntrendPrice { inc.downtrendPrice = math.Min(inc.downtrendPrice, inc.previousDowntrendPrice) } @@ -115,7 +111,7 @@ func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) { } // Update signal - if inc.AverageTrueRange.Last() <= 0 { + if inc.AverageTrueRange.Last(0) <= 0 { inc.tradeSignal = types.DirectionNone } else if inc.trend == types.DirectionUp && inc.previousTrend == types.DirectionDown { inc.tradeSignal = types.DirectionUp @@ -138,7 +134,7 @@ func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) { logst.Debugf("Update supertrend result: closePrice: %v, uptrendPrice: %v, downtrendPrice: %v, trend: %v,"+ " tradeSignal: %v, AverageTrueRange.Last(): %v", inc.closePrice, inc.uptrendPrice, inc.downtrendPrice, - inc.trend, inc.tradeSignal, inc.AverageTrueRange.Last()) + inc.trend, inc.tradeSignal, inc.AverageTrueRange.Last(0)) } func (inc *Supertrend) GetSignal() types.Direction { @@ -152,12 +148,12 @@ func (inc *Supertrend) Direction() types.Direction { // LastSupertrendSupport return the current supertrend support func (inc *Supertrend) LastSupertrendSupport() float64 { - return inc.supportLine.Last() + return inc.supportLine.Last(0) } // LastSupertrendResistance return the current supertrend resistance func (inc *Supertrend) LastSupertrendResistance() float64 { - return inc.resistanceLine.Last() + return inc.resistanceLine.Last(0) } var _ types.SeriesExtend = &Supertrend{} @@ -169,7 +165,7 @@ func (inc *Supertrend) PushK(k types.KLine) { inc.Update(k.GetHigh().Float64(), k.GetLow().Float64(), k.GetClose().Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } @@ -192,7 +188,7 @@ func (inc *Supertrend) CalculateAndUpdate(kLines []types.KLine) { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) inc.EndTime = kLines[len(kLines)-1].EndTime.Time() } diff --git a/pkg/indicator/supertrendPivot.go b/pkg/indicator/supertrendPivot.go index 14b62925d9..85b68ac92f 100644 --- a/pkg/indicator/supertrendPivot.go +++ b/pkg/indicator/supertrendPivot.go @@ -50,16 +50,12 @@ type PivotSupertrend struct { UpdateCallbacks []func(value float64) } -func (inc *PivotSupertrend) Last() float64 { - return inc.trendPrices.Last() +func (inc *PivotSupertrend) Last(i int) float64 { + return inc.trendPrices.Last(i) } func (inc *PivotSupertrend) Index(i int) float64 { - length := inc.Length() - if length == 0 || length-i-1 < 0 { - return 0 - } - return inc.trendPrices[length-i-1] + return inc.Last(i) } func (inc *PivotSupertrend) Length() int { @@ -80,8 +76,8 @@ func (inc *PivotSupertrend) Update(highPrice, lowPrice, closePrice float64) { inc.trend = types.DirectionUp } - inc.previousPivotLow = inc.PivotLow.Last() - inc.previousPivotHigh = inc.PivotHigh.Last() + inc.previousPivotLow = inc.PivotLow.Last(0) + inc.previousPivotHigh = inc.PivotHigh.Last(0) // Update High / Low pivots inc.PivotLow.Update(lowPrice) @@ -101,9 +97,9 @@ func (inc *PivotSupertrend) Update(highPrice, lowPrice, closePrice float64) { // Initialize lastPp as soon as pivots are made if inc.lastPp == 0 || math.IsNaN(inc.lastPp) { if inc.PivotHigh.Length() > 0 { - inc.lastPp = inc.PivotHigh.Last() + inc.lastPp = inc.PivotHigh.Last(0) } else if inc.PivotLow.Length() > 0 { - inc.lastPp = inc.PivotLow.Last() + inc.lastPp = inc.PivotLow.Last(0) } else { inc.lastPp = math.NaN() return @@ -111,28 +107,28 @@ func (inc *PivotSupertrend) Update(highPrice, lowPrice, closePrice float64) { } // Set lastPp to the latest pivotPoint (only changed when new pivot is found) - if inc.PivotHigh.Last() != inc.previousPivotHigh { - inc.lastPp = inc.PivotHigh.Last() - } else if inc.PivotLow.Last() != inc.previousPivotLow { - inc.lastPp = inc.PivotLow.Last() + if inc.PivotHigh.Last(0) != inc.previousPivotHigh { + inc.lastPp = inc.PivotHigh.Last(0) + } else if inc.PivotLow.Last(0) != inc.previousPivotLow { + inc.lastPp = inc.PivotLow.Last(0) } // calculate the Center line using pivot points if inc.src == 0 || math.IsNaN(inc.src) { inc.src = inc.lastPp } else { - //weighted calculation + // weighted calculation inc.src = (inc.src*2 + inc.lastPp) / 3 } // Update uptrend - inc.uptrendPrice = inc.src - inc.AverageTrueRange.Last()*inc.ATRMultiplier + inc.uptrendPrice = inc.src - inc.AverageTrueRange.Last(0)*inc.ATRMultiplier if inc.previousClosePrice > inc.previousUptrendPrice { inc.uptrendPrice = math.Max(inc.uptrendPrice, inc.previousUptrendPrice) } // Update downtrend - inc.downtrendPrice = inc.src + inc.AverageTrueRange.Last()*inc.ATRMultiplier + inc.downtrendPrice = inc.src + inc.AverageTrueRange.Last(0)*inc.ATRMultiplier if inc.previousClosePrice < inc.previousDowntrendPrice { inc.downtrendPrice = math.Min(inc.downtrendPrice, inc.previousDowntrendPrice) } @@ -147,7 +143,7 @@ func (inc *PivotSupertrend) Update(highPrice, lowPrice, closePrice float64) { } // Update signal - if inc.AverageTrueRange.Last() <= 0 { + if inc.AverageTrueRange.Last(0) <= 0 { inc.tradeSignal = types.DirectionNone } else if inc.trend == types.DirectionUp && inc.previousTrend == types.DirectionDown { inc.tradeSignal = types.DirectionUp @@ -170,7 +166,7 @@ func (inc *PivotSupertrend) Update(highPrice, lowPrice, closePrice float64) { logpst.Debugf("Update pivot point supertrend result: closePrice: %v, uptrendPrice: %v, downtrendPrice: %v, trend: %v,"+ " tradeSignal: %v, AverageTrueRange.Last(): %v", inc.closePrice, inc.uptrendPrice, inc.downtrendPrice, - inc.trend, inc.tradeSignal, inc.AverageTrueRange.Last()) + inc.trend, inc.tradeSignal, inc.AverageTrueRange.Last(0)) } // GetSignal returns signal (Down, None or Up) @@ -185,12 +181,12 @@ func (inc *PivotSupertrend) Direction() types.Direction { // LastSupertrendSupport return the current supertrend support value func (inc *PivotSupertrend) LastSupertrendSupport() float64 { - return inc.supportLine.Last() + return inc.supportLine.Last(0) } // LastSupertrendResistance return the current supertrend resistance value func (inc *PivotSupertrend) LastSupertrendResistance() float64 { - return inc.resistanceLine.Last() + return inc.resistanceLine.Last(0) } var _ types.SeriesExtend = &PivotSupertrend{} @@ -202,7 +198,7 @@ func (inc *PivotSupertrend) PushK(k types.KLine) { inc.Update(k.GetHigh().Float64(), k.GetLow().Float64(), k.GetClose().Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func (inc *PivotSupertrend) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) { diff --git a/pkg/indicator/tema.go b/pkg/indicator/tema.go index a3a7024092..f22a4c93a2 100644 --- a/pkg/indicator/tema.go +++ b/pkg/indicator/tema.go @@ -36,26 +36,20 @@ func (inc *TEMA) Update(value float64) { inc.A3 = &EWMA{IntervalWindow: inc.IntervalWindow} } inc.A1.Update(value) - a1 := inc.A1.Last() + a1 := inc.A1.Last(0) inc.A2.Update(a1) - a2 := inc.A2.Last() + a2 := inc.A2.Last(0) inc.A3.Update(a2) - a3 := inc.A3.Last() + a3 := inc.A3.Last(0) inc.Values.Push(3*a1 - 3*a2 + a3) } -func (inc *TEMA) Last() float64 { - if len(inc.Values) > 0 { - return inc.Values[len(inc.Values)-1] - } - return 0.0 +func (inc *TEMA) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *TEMA) Index(i int) float64 { - if i >= len(inc.Values) { - return 0 - } - return inc.Values[len(inc.Values)-i-1] + return inc.Last(i) } func (inc *TEMA) Length() int { @@ -72,12 +66,12 @@ func (inc *TEMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.A1 == nil { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/tema_test.go b/pkg/indicator/tema_test.go index b50c72ca75..3012f55391 100644 --- a/pkg/indicator/tema_test.go +++ b/pkg/indicator/tema_test.go @@ -47,7 +47,7 @@ func Test_TEMA(t *testing.T) { t.Run(tt.name, func(t *testing.T) { tema := TEMA{IntervalWindow: types.IntervalWindow{Window: 16}} tema.CalculateAndUpdate(tt.kLines) - last := tema.Last() + last := tema.Last(0) assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, tema.Index(1), Delta) assert.Equal(t, tt.all, tema.Length()) diff --git a/pkg/indicator/till.go b/pkg/indicator/till.go index c7fca34dcb..828175dca5 100644 --- a/pkg/indicator/till.go +++ b/pkg/indicator/till.go @@ -57,28 +57,18 @@ func (inc *TILL) Update(value float64) { } inc.e1.Update(value) - inc.e2.Update(inc.e1.Last()) - inc.e3.Update(inc.e2.Last()) - inc.e4.Update(inc.e3.Last()) - inc.e5.Update(inc.e4.Last()) - inc.e6.Update(inc.e5.Last()) + inc.e2.Update(inc.e1.Last(0)) + inc.e3.Update(inc.e2.Last(0)) + inc.e4.Update(inc.e3.Last(0)) + inc.e5.Update(inc.e4.Last(0)) + inc.e6.Update(inc.e5.Last(0)) } -func (inc *TILL) Last() float64 { - if inc.e1 == nil || inc.e1.Length() == 0 { - return 0 - } - e3 := inc.e3.Last() - e4 := inc.e4.Last() - e5 := inc.e5.Last() - e6 := inc.e6.Last() - return inc.c1*e6 + inc.c2*e5 + inc.c3*e4 + inc.c4*e3 -} - -func (inc *TILL) Index(i int) float64 { +func (inc *TILL) Last(i int) float64 { if inc.e1 == nil || inc.e1.Length() <= i { return 0 } + e3 := inc.e3.Index(i) e4 := inc.e4.Index(i) e5 := inc.e5.Index(i) @@ -86,6 +76,10 @@ func (inc *TILL) Index(i int) float64 { return inc.c1*e6 + inc.c2*e5 + inc.c3*e4 + inc.c4*e3 } +func (inc *TILL) Index(i int) float64 { + return inc.Last(i) +} + func (inc *TILL) Length() int { if inc.e1 == nil { return 0 @@ -101,7 +95,7 @@ func (inc *TILL) PushK(k types.KLine) { } inc.Update(k.Close.Float64()) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func (inc *TILL) LoadK(allKLines []types.KLine) { diff --git a/pkg/indicator/till_test.go b/pkg/indicator/till_test.go index 1b03017b80..e78e6620a7 100644 --- a/pkg/indicator/till_test.go +++ b/pkg/indicator/till_test.go @@ -57,7 +57,7 @@ func Test_TILL(t *testing.T) { t.Run(tt.name, func(t *testing.T) { till := TILL{IntervalWindow: types.IntervalWindow{Window: 16}} till.CalculateAndUpdate(tt.kLines) - last := till.Last() + last := till.Last(0) assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, till.Index(1), Delta) assert.Equal(t, tt.all, till.Length()) diff --git a/pkg/indicator/tma.go b/pkg/indicator/tma.go index 97c5997d57..fd1c416dba 100644 --- a/pkg/indicator/tma.go +++ b/pkg/indicator/tma.go @@ -6,6 +6,7 @@ import ( // Refer: Triangular Moving Average // Refer URL: https://ja.wikipedia.org/wiki/移動平均 +// //go:generate callbackgen -type TMA type TMA struct { types.SeriesBase @@ -24,21 +25,15 @@ func (inc *TMA) Update(value float64) { } inc.s1.Update(value) - inc.s2.Update(inc.s1.Last()) + inc.s2.Update(inc.s1.Last(0)) } -func (inc *TMA) Last() float64 { - if inc.s2 == nil { - return 0 - } - return inc.s2.Last() +func (inc *TMA) Last(i int) float64 { + return inc.s2.Last(i) } func (inc *TMA) Index(i int) float64 { - if inc.s2 == nil { - return 0 - } - return inc.s2.Index(i) + return inc.Last(i) } func (inc *TMA) Length() int { @@ -58,12 +53,12 @@ func (inc *TMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.s1 == nil { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/tsi.go b/pkg/indicator/tsi.go index 4cdb6c7508..fa4546aa74 100644 --- a/pkg/indicator/tsi.go +++ b/pkg/indicator/tsi.go @@ -9,6 +9,7 @@ import ( // Refer: True Strength Index // Refer URL: https://www.investopedia.com/terms/t/tsi.asp +// //go:generate callbackgen -type TSI type TSI struct { types.SeriesBase @@ -66,10 +67,10 @@ func (inc *TSI) Update(value float64) { apc := math.Abs(pc) inc.Apcs.Update(apc) - inc.Pcds.Update(inc.Pcs.Last()) - inc.Apcds.Update(inc.Apcs.Last()) + inc.Pcds.Update(inc.Pcs.Last(0)) + inc.Apcds.Update(inc.Apcs.Last(0)) - tsi := (inc.Pcds.Last() / inc.Apcds.Last()) * 100. + tsi := (inc.Pcds.Last(0) / inc.Apcds.Last(0)) * 100. inc.Values.Push(tsi) if inc.Values.Length() > MaxNumOfEWMA { inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] @@ -80,12 +81,12 @@ func (inc *TSI) Length() int { return inc.Values.Length() } -func (inc *TSI) Last() float64 { - return inc.Values.Last() +func (inc *TSI) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *TSI) Index(i int) float64 { - return inc.Values.Index(i) + return inc.Last(i) } func (inc *TSI) PushK(k types.KLine) { diff --git a/pkg/indicator/tsi_test.go b/pkg/indicator/tsi_test.go index dc7c9c3cb1..872cfbca00 100644 --- a/pkg/indicator/tsi_test.go +++ b/pkg/indicator/tsi_test.go @@ -32,5 +32,5 @@ func Test_TSI(t *testing.T) { } assert.Equal(t, tsi.Length(), 29) Delta := 1.5e-2 - assert.InDelta(t, tsi.Last(), 22.89, Delta) + assert.InDelta(t, tsi.Last(0), 22.89, Delta) } diff --git a/pkg/indicator/utBotAlert.go b/pkg/indicator/utBotAlert.go index 9d974ca421..4a9c5cda47 100644 --- a/pkg/indicator/utBotAlert.go +++ b/pkg/indicator/utBotAlert.go @@ -70,20 +70,20 @@ func (inc *UtBotAlert) Update(highPrice, lowPrice, closePrice float64) { // Update ATR inc.AverageTrueRange.Update(highPrice, lowPrice, closePrice) - nLoss := inc.AverageTrueRange.Last() * inc.KeyValue + nLoss := inc.AverageTrueRange.Last(0) * inc.KeyValue // xATRTrailingStop if inc.xATRTrailingStop.Length() == 0 { // For first run inc.xATRTrailingStop.Update(0) - } else if closePrice > inc.xATRTrailingStop.Index(1) && inc.previousClosePrice > inc.xATRTrailingStop.Index(1) { - inc.xATRTrailingStop.Update(math.Max(inc.xATRTrailingStop.Index(1), closePrice-nLoss)) + } else if closePrice > inc.xATRTrailingStop.Last(1) && inc.previousClosePrice > inc.xATRTrailingStop.Last(1) { + inc.xATRTrailingStop.Update(math.Max(inc.xATRTrailingStop.Last(1), closePrice-nLoss)) - } else if closePrice < inc.xATRTrailingStop.Index(1) && inc.previousClosePrice < inc.xATRTrailingStop.Index(1) { - inc.xATRTrailingStop.Update(math.Min(inc.xATRTrailingStop.Index(1), closePrice+nLoss)) + } else if closePrice < inc.xATRTrailingStop.Last(1) && inc.previousClosePrice < inc.xATRTrailingStop.Last(1) { + inc.xATRTrailingStop.Update(math.Min(inc.xATRTrailingStop.Last(1), closePrice+nLoss)) - } else if closePrice > inc.xATRTrailingStop.Index(1) { + } else if closePrice > inc.xATRTrailingStop.Last(1) { inc.xATRTrailingStop.Update(closePrice - nLoss) } else { @@ -91,19 +91,19 @@ func (inc *UtBotAlert) Update(highPrice, lowPrice, closePrice float64) { } // pos - if inc.previousClosePrice < inc.xATRTrailingStop.Index(1) && closePrice > inc.xATRTrailingStop.Index(1) { + if inc.previousClosePrice < inc.xATRTrailingStop.Last(1) && closePrice > inc.xATRTrailingStop.Last(1) { inc.pos = types.DirectionUp - } else if inc.previousClosePrice > inc.xATRTrailingStop.Index(1) && closePrice < inc.xATRTrailingStop.Index(1) { + } else if inc.previousClosePrice > inc.xATRTrailingStop.Last(1) && closePrice < inc.xATRTrailingStop.Last(1) { inc.pos = types.DirectionDown } else { inc.pos = inc.previousPos } - above := closePrice > inc.xATRTrailingStop.Last() && inc.previousClosePrice < inc.xATRTrailingStop.Index(1) - below := closePrice < inc.xATRTrailingStop.Last() && inc.previousClosePrice > inc.xATRTrailingStop.Index(1) + above := closePrice > inc.xATRTrailingStop.Last(0) && inc.previousClosePrice < inc.xATRTrailingStop.Last(1) + below := closePrice < inc.xATRTrailingStop.Last(0) && inc.previousClosePrice > inc.xATRTrailingStop.Last(1) - buy := closePrice > inc.xATRTrailingStop.Last() && above // buy - sell := closePrice < inc.xATRTrailingStop.Last() && below // sell + buy := closePrice > inc.xATRTrailingStop.Last(0) && above // buy + sell := closePrice < inc.xATRTrailingStop.Last(0) && below // sell inc.buyValue.Push(buy) inc.sellValue.Push(sell) diff --git a/pkg/indicator/v2_atr_test.go b/pkg/indicator/v2_atr_test.go index 1dd49501a2..bacbc78612 100644 --- a/pkg/indicator/v2_atr_test.go +++ b/pkg/indicator/v2_atr_test.go @@ -71,7 +71,7 @@ func Test_ATR2(t *testing.T) { stream.EmitKLineClosed(k) } - got := atr.Last() + got := atr.Last(0) diff := math.Trunc((got-tt.want)*100) / 100 if diff != 0 { t.Errorf("ATR2() = %v, want %v", got, tt.want) diff --git a/pkg/indicator/v2_ewma.go b/pkg/indicator/v2_ewma.go index e452aaedae..afd0752ad7 100644 --- a/pkg/indicator/v2_ewma.go +++ b/pkg/indicator/v2_ewma.go @@ -30,7 +30,7 @@ func (s *EWMAStream) calculateAndPush(v float64) { } func (s *EWMAStream) calculate(v float64) float64 { - last := s.slice.Last() + last := s.slice.Last(0) m := s.multiplier return (1.0-m)*last + m*v } diff --git a/pkg/indicator/v2_rsi.go b/pkg/indicator/v2_rsi.go index 16732f2bdc..583e68bafe 100644 --- a/pkg/indicator/v2_rsi.go +++ b/pkg/indicator/v2_rsi.go @@ -32,8 +32,8 @@ func (s *RSIStream) calculate(_ float64) float64 { var sourceLen = s.source.Length() var limit = min(s.window, sourceLen) for i := 0; i < limit; i++ { - value := s.source.Index(i) - prev := s.source.Index(i + 1) + value := s.source.Last(i) + prev := s.source.Last(i + 1) change := value - prev if change >= 0 { gainSum += change diff --git a/pkg/indicator/v2_tr_test.go b/pkg/indicator/v2_tr_test.go index 38195f55d7..c6b46332ab 100644 --- a/pkg/indicator/v2_tr_test.go +++ b/pkg/indicator/v2_tr_test.go @@ -72,7 +72,7 @@ func Test_TR_and_RMA(t *testing.T) { stream.EmitKLineClosed(k) } - got := rma.Last() + got := rma.Last(0) diff := math.Trunc((got-tt.want)*100) / 100 if diff != 0 { t.Errorf("RMA(TR()) = %v, want %v", got, tt.want) diff --git a/pkg/indicator/vidya.go b/pkg/indicator/vidya.go index 311e47f4fb..5f21be1c0f 100644 --- a/pkg/indicator/vidya.go +++ b/pkg/indicator/vidya.go @@ -58,18 +58,18 @@ func (inc *VIDYA) Update(value float64) { change := types.Change(&inc.input) CMO := math.Abs(types.Sum(change, inc.Window) / types.Sum(types.Abs(change), inc.Window)) alpha := 2. / float64(inc.Window+1) - inc.Values.Push(value*alpha*CMO + inc.Values.Last()*(1.-alpha*CMO)) + inc.Values.Push(value*alpha*CMO + inc.Values.Last(0)*(1.-alpha*CMO)) if inc.Values.Length() > MaxNumOfEWMA { inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] } } -func (inc *VIDYA) Last() float64 { - return inc.Values.Last() +func (inc *VIDYA) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *VIDYA) Index(i int) float64 { - return inc.Values.Index(i) + return inc.Last(i) } func (inc *VIDYA) Length() int { @@ -86,12 +86,12 @@ func (inc *VIDYA) CalculateAndUpdate(allKLines []types.KLine) { if inc.input.Length() == 0 { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/vidya_test.go b/pkg/indicator/vidya_test.go index 8ac1df255d..164173441d 100644 --- a/pkg/indicator/vidya_test.go +++ b/pkg/indicator/vidya_test.go @@ -10,10 +10,10 @@ import ( func Test_VIDYA(t *testing.T) { vidya := &VIDYA{IntervalWindow: types.IntervalWindow{Window: 16}} vidya.Update(1) - assert.Equal(t, vidya.Last(), 1.) + assert.Equal(t, vidya.Last(0), 1.) vidya.Update(2) newV := 2./17.*2. + 1.*(1.-2./17.) - assert.Equal(t, vidya.Last(), newV) + assert.Equal(t, vidya.Last(0), newV) vidya.Update(1) - assert.Equal(t, vidya.Last(), vidya.Index(1)) + assert.Equal(t, vidya.Last(0), vidya.Index(1)) } diff --git a/pkg/indicator/volatility.go b/pkg/indicator/volatility.go index 6b3dcd7cb1..3c5efce962 100644 --- a/pkg/indicator/volatility.go +++ b/pkg/indicator/volatility.go @@ -26,18 +26,12 @@ type Volatility struct { UpdateCallbacks []func(value float64) } -func (inc *Volatility) Last() float64 { - if len(inc.Values) == 0 { - return 0.0 - } - return inc.Values[len(inc.Values)-1] +func (inc *Volatility) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *Volatility) Index(i int) float64 { - if len(inc.Values)-i <= 0 { - return 0.0 - } - return inc.Values[len(inc.Values)-i-1] + return inc.Last(i) } func (inc *Volatility) Length() int { diff --git a/pkg/indicator/vwap.go b/pkg/indicator/vwap.go index 805def8285..3f2e08085c 100644 --- a/pkg/indicator/vwap.go +++ b/pkg/indicator/vwap.go @@ -56,20 +56,12 @@ func (inc *VWAP) Update(price, volume float64) { inc.Values.Push(vwap) } -func (inc *VWAP) Last() float64 { - if len(inc.Values) == 0 { - return 0.0 - } - return inc.Values[len(inc.Values)-1] +func (inc *VWAP) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *VWAP) Index(i int) float64 { - length := len(inc.Values) - if length == 0 || length-i-1 < 0 { - return 0 - } - - return inc.Values[length-i-1] + return inc.Last(i) } func (inc *VWAP) Length() int { @@ -91,7 +83,7 @@ func (inc *VWAP) CalculateAndUpdate(allKLines []types.KLine) { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) inc.EndTime = allKLines[len(allKLines)-1].EndTime.Time() } @@ -112,5 +104,5 @@ func calculateVWAP(klines []types.KLine, priceF KLineValueMapper, window int) fl for _, k := range klines { vwap.Update(priceF(k), k.Volume.Float64()) } - return vwap.Last() + return vwap.Last(0) } diff --git a/pkg/indicator/vwma.go b/pkg/indicator/vwma.go index a8a328e20f..ed383531bc 100644 --- a/pkg/indicator/vwma.go +++ b/pkg/indicator/vwma.go @@ -14,7 +14,7 @@ import ( // VWMA = SMA(pv, window) / SMA(volumes, window) // // Volume Weighted Moving Average -//- https://www.motivewave.com/studies/volume_weighted_moving_average.htm +// - https://www.motivewave.com/studies/volume_weighted_moving_average.htm // // The Volume Weighted Moving Average (VWMA) is a technical analysis indicator that is used to smooth price data and reduce the lag // associated with traditional moving averages. It is calculated by taking the weighted moving average of the input data, with the @@ -36,19 +36,12 @@ type VWMA struct { updateCallbacks []func(value float64) } -func (inc *VWMA) Last() float64 { - if len(inc.Values) == 0 { - return 0.0 - } - return inc.Values[len(inc.Values)-1] +func (inc *VWMA) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *VWMA) Index(i int) float64 { - length := len(inc.Values) - if length == 0 || length-i-1 < 0 { - return 0 - } - return inc.Values[length-i-1] + return inc.Last(i) } func (inc *VWMA) Length() int { @@ -70,8 +63,8 @@ func (inc *VWMA) Update(price, volume float64) { inc.PriceVolumeSMA.Update(price * volume) inc.VolumeSMA.Update(volume) - pv := inc.PriceVolumeSMA.Last() - v := inc.VolumeSMA.Last() + pv := inc.PriceVolumeSMA.Last(0) + v := inc.VolumeSMA.Last(0) vwma := pv / v inc.Values.Push(vwma) } @@ -104,7 +97,7 @@ func (inc *VWMA) CalculateAndUpdate(allKLines []types.KLine) { } inc.EndTime = last.EndTime.Time() - inc.EmitUpdate(inc.Values.Last()) + inc.EmitUpdate(inc.Values.Last(0)) } func (inc *VWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { diff --git a/pkg/indicator/wdrift.go b/pkg/indicator/wdrift.go index 61060a7470..d9b6675a06 100644 --- a/pkg/indicator/wdrift.go +++ b/pkg/indicator/wdrift.go @@ -10,6 +10,7 @@ import ( // Refer: https://tradingview.com/script/aDymGrFx-Drift-Study-Inspired-by-Monte-Carlo-Simulations-with-BM-KL/ // Brownian Motion's drift factor // could be used in Monte Carlo Simulations +// //go:generate callbackgen -type WeightedDrift type WeightedDrift struct { types.SeriesBase @@ -54,7 +55,7 @@ func (inc *WeightedDrift) Update(value float64, weight float64) { } if inc.chng.Length() >= inc.Window { stdev := types.Stdev(inc.chng, inc.Window) - drift := inc.MA.Last() - stdev*stdev*0.5 + drift := inc.MA.Last(0) - stdev*stdev*0.5 inc.Values.Push(drift) } } @@ -77,7 +78,7 @@ func (inc *WeightedDrift) ZeroPoint() float64 { } else { return N2 }*/ - return inc.LastValue * math.Exp(window*(0.5*stdev*stdev)+chng-inc.MA.Last()*window) + return inc.LastValue * math.Exp(window*(0.5*stdev*stdev)+chng-inc.MA.Last(0)*window) } func (inc *WeightedDrift) Clone() (out *WeightedDrift) { @@ -100,17 +101,11 @@ func (inc *WeightedDrift) TestUpdate(value float64, weight float64) *WeightedDri } func (inc *WeightedDrift) Index(i int) float64 { - if inc.Values == nil { - return 0 - } - return inc.Values.Index(i) + return inc.Last(i) } -func (inc *WeightedDrift) Last() float64 { - if inc.Values.Length() == 0 { - return 0 - } - return inc.Values.Last() +func (inc *WeightedDrift) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *WeightedDrift) Length() int { @@ -130,12 +125,12 @@ func (inc *WeightedDrift) CalculateAndUpdate(allKLines []types.KLine) { if inc.chng == nil { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/wwma.go b/pkg/indicator/wwma.go index 0dbc67ca1e..f8fe954aa1 100644 --- a/pkg/indicator/wwma.go +++ b/pkg/indicator/wwma.go @@ -33,25 +33,17 @@ func (inc *WWMA) Update(value float64) { inc.Values = inc.Values[MaxNumOfWWMATruncateSize-1:] } - last := inc.Last() + last := inc.Last(0) wma := last + (value-last)/float64(inc.Window) inc.Values.Push(wma) } -func (inc *WWMA) Last() float64 { - if len(inc.Values) == 0 { - return 0 - } - - return inc.Values[len(inc.Values)-1] +func (inc *WWMA) Last(i int) float64 { + return inc.Values.Last(i) } func (inc *WWMA) Index(i int) float64 { - if i >= len(inc.Values) { - return 0 - } - - return inc.Values[len(inc.Values)-1-i] + return inc.Last(i) } func (inc *WWMA) Length() int { @@ -76,7 +68,7 @@ func (inc *WWMA) CalculateAndUpdate(allKLines []types.KLine) { if doable { inc.PushK(k) inc.LastOpenTime = k.StartTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } } diff --git a/pkg/indicator/zlema.go b/pkg/indicator/zlema.go index 5c7279f619..182cf29eb5 100644 --- a/pkg/indicator/zlema.go +++ b/pkg/indicator/zlema.go @@ -27,17 +27,14 @@ type ZLEMA struct { } func (inc *ZLEMA) Index(i int) float64 { - if inc.zlema == nil { - return 0 - } - return inc.zlema.Index(i) + return inc.Last(i) } -func (inc *ZLEMA) Last() float64 { +func (inc *ZLEMA) Last(i int) float64 { if inc.zlema == nil { return 0 } - return inc.zlema.Last() + return inc.zlema.Last(i) } func (inc *ZLEMA) Length() int { @@ -74,12 +71,12 @@ func (inc *ZLEMA) CalculateAndUpdate(allKLines []types.KLine) { if inc.zlema == nil { for _, k := range allKLines { inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } diff --git a/pkg/indicator/zlema_test.go b/pkg/indicator/zlema_test.go index 4560f4276b..6f2ff31946 100644 --- a/pkg/indicator/zlema_test.go +++ b/pkg/indicator/zlema_test.go @@ -46,7 +46,7 @@ func Test_ZLEMA(t *testing.T) { t.Run(tt.name, func(t *testing.T) { zlema := ZLEMA{IntervalWindow: types.IntervalWindow{Window: 16}} zlema.CalculateAndUpdate(tt.kLines) - last := zlema.Last() + last := zlema.Last(0) assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.next, zlema.Index(1), Delta) assert.Equal(t, tt.all, zlema.Length()) diff --git a/pkg/risk/dynamicrisk/dynamic_exposure.go b/pkg/risk/dynamicrisk/dynamic_exposure.go index 71eb121557..415a287a3b 100644 --- a/pkg/risk/dynamicrisk/dynamic_exposure.go +++ b/pkg/risk/dynamicrisk/dynamic_exposure.go @@ -59,9 +59,9 @@ func (d *DynamicExposureBollBand) initialize(symbol string, session *bbgo.Exchan // getMaxExposure returns the max exposure func (d *DynamicExposureBollBand) getMaxExposure(price float64, trend types.Direction) (fixedpoint.Value, error) { - downBand := d.dynamicExposureBollBand.DownBand.Last() - upBand := d.dynamicExposureBollBand.UpBand.Last() - sma := d.dynamicExposureBollBand.SMA.Last() + downBand := d.dynamicExposureBollBand.DownBand.Last(0) + upBand := d.dynamicExposureBollBand.UpBand.Last(0) + sma := d.dynamicExposureBollBand.SMA.Last(0) log.Infof("dynamicExposureBollBand bollinger band: up %f sma %f down %f", upBand, sma, downBand) bandPercentage := 0.0 diff --git a/pkg/risk/dynamicrisk/dynamic_spread.go b/pkg/risk/dynamicrisk/dynamic_spread.go index 2b5cbbeb89..f6fab9cb6f 100644 --- a/pkg/risk/dynamicrisk/dynamic_spread.go +++ b/pkg/risk/dynamicrisk/dynamic_spread.go @@ -114,7 +114,7 @@ func (ds *DynamicAmpSpread) update(kline types.KLine) { func (ds *DynamicAmpSpread) getAskSpread() (askSpread float64, err error) { if ds.AskSpreadScale != nil && ds.dynamicAskSpread.Length() >= ds.Window { - askSpread, err = ds.AskSpreadScale.Scale(ds.dynamicAskSpread.Last()) + askSpread, err = ds.AskSpreadScale.Scale(ds.dynamicAskSpread.Last(0)) if err != nil { log.WithError(err).Errorf("can not calculate dynamicAskSpread") return 0, err @@ -128,7 +128,7 @@ func (ds *DynamicAmpSpread) getAskSpread() (askSpread float64, err error) { func (ds *DynamicAmpSpread) getBidSpread() (bidSpread float64, err error) { if ds.BidSpreadScale != nil && ds.dynamicBidSpread.Length() >= ds.Window { - bidSpread, err = ds.BidSpreadScale.Scale(ds.dynamicBidSpread.Last()) + bidSpread, err = ds.BidSpreadScale.Scale(ds.dynamicBidSpread.Last(0)) if err != nil { log.WithError(err).Errorf("can not calculate dynamicBidSpread") return 0, err @@ -224,12 +224,12 @@ func (ds *DynamicSpreadBollWidthRatio) getWeightedBBWidthRatio(positiveSigmoid b // - To ask spread, the higher neutral band get greater ratio // - To bid spread, the lower neutral band get greater ratio - defaultMid := ds.defaultBoll.SMA.Last() - defaultUpper := ds.defaultBoll.UpBand.Last() - defaultLower := ds.defaultBoll.DownBand.Last() + defaultMid := ds.defaultBoll.SMA.Last(0) + defaultUpper := ds.defaultBoll.UpBand.Last(0) + defaultLower := ds.defaultBoll.DownBand.Last(0) defaultWidth := defaultUpper - defaultLower - neutralUpper := ds.neutralBoll.UpBand.Last() - neutralLower := ds.neutralBoll.DownBand.Last() + neutralUpper := ds.neutralBoll.UpBand.Last(0) + neutralLower := ds.neutralBoll.DownBand.Last(0) factor := defaultWidth / ds.Sensitivity var weightedUpper, weightedLower, weightedDivUpper, weightedDivLower float64 if positiveSigmoid { diff --git a/pkg/strategy/audacitymaker/orderflow.go b/pkg/strategy/audacitymaker/orderflow.go index b8b0d80c7d..94de688805 100644 --- a/pkg/strategy/audacitymaker/orderflow.go +++ b/pkg/strategy/audacitymaker/orderflow.go @@ -92,7 +92,7 @@ func (s *PerTrade) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener // min-max scaling ofsMax := orderFlowSize.Tail(100).Max() ofsMin := orderFlowSize.Tail(100).Min() - ofsMinMax := (orderFlowSize.Last() - ofsMin) / (ofsMax - ofsMin) + ofsMinMax := (orderFlowSize.Last(0) - ofsMin) / (ofsMax - ofsMin) // preserves temporal dependency via polar encoded angles orderFlowSizeMinMax.Push(ofsMinMax) } @@ -102,7 +102,7 @@ func (s *PerTrade) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener // min-max scaling ofnMax := orderFlowNumber.Tail(100).Max() ofnMin := orderFlowNumber.Tail(100).Min() - ofnMinMax := (orderFlowNumber.Last() - ofnMin) / (ofnMax - ofnMin) + ofnMinMax := (orderFlowNumber.Last(0) - ofnMin) / (ofnMax - ofnMin) // preserves temporal dependency via polar encoded angles orderFlowNumberMinMax.Push(ofnMinMax) } @@ -167,9 +167,9 @@ func (s *PerTrade) placeTrade(ctx context.Context, side types.SideType, quantity func outlier(fs floats.Slice, multiplier float64) int { stddev := stat.StdDev(fs, nil) - if fs.Last() > fs.Mean()+multiplier*stddev { + if fs.Last(0) > fs.Mean()+multiplier*stddev { return 1 - } else if fs.Last() < fs.Mean()-multiplier*stddev { + } else if fs.Last(0) < fs.Mean()-multiplier*stddev { return -1 } return 0 diff --git a/pkg/strategy/bollgrid/strategy.go b/pkg/strategy/bollgrid/strategy.go index 61be952022..2474c7b9cc 100644 --- a/pkg/strategy/bollgrid/strategy.go +++ b/pkg/strategy/bollgrid/strategy.go @@ -135,7 +135,7 @@ func (s *Strategy) generateGridBuyOrders(session *bbgo.ExchangeSession) ([]types ema99 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 99}) ema25 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 25}) ema7 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 7}) - if ema7.Last() > ema25.Last()*1.001 && ema25.Last() > ema99.Last()*1.0005 { + if ema7.Last(0) > ema25.Last(0)*1.001 && ema25.Last(0) > ema99.Last(0)*1.0005 { log.Infof("all ema lines trend up, skip buy") return nil, nil } @@ -202,7 +202,7 @@ func (s *Strategy) generateGridSellOrders(session *bbgo.ExchangeSession) ([]type ema99 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 99}) ema25 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 25}) ema7 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 7}) - if ema7.Last() < ema25.Last()*(1-0.004) && ema25.Last() < ema99.Last()*(1-0.0005) { + if ema7.Last(0) < ema25.Last(0)*(1-0.004) && ema25.Last(0) < ema99.Last(0)*(1-0.0005) { log.Infof("all ema lines trend down, skip sell") return nil, nil } diff --git a/pkg/strategy/bollmaker/dynamic_spread.go b/pkg/strategy/bollmaker/dynamic_spread.go index f7583d1105..51785f3df9 100644 --- a/pkg/strategy/bollmaker/dynamic_spread.go +++ b/pkg/strategy/bollmaker/dynamic_spread.go @@ -126,7 +126,7 @@ func (ds *DynamicSpreadAmpSettings) update(kline types.KLine) { func (ds *DynamicSpreadAmpSettings) getAskSpread() (askSpread float64, err error) { if ds.AskSpreadScale != nil && ds.dynamicAskSpread.Length() >= ds.Window { - askSpread, err = ds.AskSpreadScale.Scale(ds.dynamicAskSpread.Last()) + askSpread, err = ds.AskSpreadScale.Scale(ds.dynamicAskSpread.Last(0)) if err != nil { log.WithError(err).Errorf("can not calculate dynamicAskSpread") return 0, err @@ -140,7 +140,7 @@ func (ds *DynamicSpreadAmpSettings) getAskSpread() (askSpread float64, err error func (ds *DynamicSpreadAmpSettings) getBidSpread() (bidSpread float64, err error) { if ds.BidSpreadScale != nil && ds.dynamicBidSpread.Length() >= ds.Window { - bidSpread, err = ds.BidSpreadScale.Scale(ds.dynamicBidSpread.Last()) + bidSpread, err = ds.BidSpreadScale.Scale(ds.dynamicBidSpread.Last(0)) if err != nil { log.WithError(err).Errorf("can not calculate dynamicBidSpread") return 0, err @@ -224,12 +224,12 @@ func (ds *DynamicSpreadBollWidthRatioSettings) getWeightedBBWidthRatio(positiveS // - To ask spread, the higher neutral band get greater ratio // - To bid spread, the lower neutral band get greater ratio - defaultMid := ds.defaultBoll.SMA.Last() - defaultUpper := ds.defaultBoll.UpBand.Last() - defaultLower := ds.defaultBoll.DownBand.Last() + defaultMid := ds.defaultBoll.SMA.Last(0) + defaultUpper := ds.defaultBoll.UpBand.Last(0) + defaultLower := ds.defaultBoll.DownBand.Last(0) defaultWidth := defaultUpper - defaultLower - neutralUpper := ds.neutralBoll.UpBand.Last() - neutralLower := ds.neutralBoll.DownBand.Last() + neutralUpper := ds.neutralBoll.UpBand.Last(0) + neutralLower := ds.neutralBoll.DownBand.Last(0) factor := defaultWidth / ds.Sensitivity var weightedUpper, weightedLower, weightedDivUpper, weightedDivLower float64 if positiveSigmoid { diff --git a/pkg/strategy/bollmaker/strategy.go b/pkg/strategy/bollmaker/strategy.go index 92b3fc4af7..efd1742d21 100644 --- a/pkg/strategy/bollmaker/strategy.go +++ b/pkg/strategy/bollmaker/strategy.go @@ -278,9 +278,9 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k baseBalance, hasBaseBalance := balances[s.Market.BaseCurrency] quoteBalance, hasQuoteBalance := balances[s.Market.QuoteCurrency] - downBand := s.defaultBoll.DownBand.Last() - upBand := s.defaultBoll.UpBand.Last() - sma := s.defaultBoll.SMA.Last() + downBand := s.defaultBoll.DownBand.Last(0) + upBand := s.defaultBoll.UpBand.Last(0) + sma := s.defaultBoll.SMA.Last(0) log.Infof("%s bollinger band: up %f sma %f down %f", s.Symbol, upBand, sma, downBand) bandPercentage := calculateBandPercentage(upBand, downBand, sma, midPrice.Float64()) @@ -351,7 +351,7 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k // WHEN: price breaks the upper band (price > window 2) == strongUpTrend // THEN: we apply strongUpTrend skew if s.TradeInBand { - if !inBetween(midPrice.Float64(), s.neutralBoll.DownBand.Last(), s.neutralBoll.UpBand.Last()) { + if !inBetween(midPrice.Float64(), s.neutralBoll.DownBand.Last(0), s.neutralBoll.UpBand.Last(0)) { log.Infof("tradeInBand is set, skip placing orders when the price is outside of the band") return } @@ -410,7 +410,7 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k canSell = false } - if s.BuyBelowNeutralSMA && midPrice.Float64() > s.neutralBoll.SMA.Last() { + if s.BuyBelowNeutralSMA && midPrice.Float64() > s.neutralBoll.SMA.Last(0) { canBuy = false } diff --git a/pkg/strategy/bollmaker/trend.go b/pkg/strategy/bollmaker/trend.go index 33167967b5..4c0cb7fcdf 100644 --- a/pkg/strategy/bollmaker/trend.go +++ b/pkg/strategy/bollmaker/trend.go @@ -12,7 +12,7 @@ const ( ) func detectPriceTrend(inc *indicator.BOLL, price float64) PriceTrend { - if inBetween(price, inc.DownBand.Last(), inc.UpBand.Last()) { + if inBetween(price, inc.DownBand.Last(0), inc.UpBand.Last(0)) { return NeutralTrend } diff --git a/pkg/strategy/drift/driftma.go b/pkg/strategy/drift/driftma.go index 0ff0a9c70b..ba0468cdcb 100644 --- a/pkg/strategy/drift/driftma.go +++ b/pkg/strategy/drift/driftma.go @@ -17,19 +17,19 @@ func (s *DriftMA) Update(value, weight float64) { if s.ma1.Length() == 0 { return } - s.drift.Update(s.ma1.Last(), weight) + s.drift.Update(s.ma1.Last(0), weight) if s.drift.Length() == 0 { return } - s.ma2.Update(s.drift.Last()) + s.ma2.Update(s.drift.Last(0)) } -func (s *DriftMA) Last() float64 { - return s.ma2.Last() +func (s *DriftMA) Last(int) float64 { + return s.ma2.Last(0) } func (s *DriftMA) Index(i int) float64 { - return s.ma2.Index(i) + return s.ma2.Last(i) } func (s *DriftMA) Length() int { diff --git a/pkg/strategy/drift/stoploss.go b/pkg/strategy/drift/stoploss.go index bd78efa1dc..56d985f08d 100644 --- a/pkg/strategy/drift/stoploss.go +++ b/pkg/strategy/drift/stoploss.go @@ -9,7 +9,7 @@ func (s *Strategy) CheckStopLoss() bool { } } if s.UseAtr { - atr := s.atr.Last() + atr := s.atr.Last(0) if s.sellPrice > 0 && s.sellPrice+atr <= s.highestPrice || s.buyPrice > 0 && s.buyPrice-atr >= s.lowestPrice { return true diff --git a/pkg/strategy/drift/strategy.go b/pkg/strategy/drift/strategy.go index 21f748e4cd..cf9c53e8e9 100644 --- a/pkg/strategy/drift/strategy.go +++ b/pkg/strategy/drift/strategy.go @@ -261,8 +261,8 @@ func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error { high := kline.High.Float64() low := kline.Low.Float64() s.ma.Update(source) - s.stdevHigh.Update(high - s.ma.Last()) - s.stdevLow.Update(s.ma.Last() - low) + s.stdevHigh.Update(high - s.ma.Last(0)) + s.stdevLow.Update(s.ma.Last(0) - low) s.drift.Update(source, kline.Volume.Abs().Float64()) s.trendLine.Update(source) s.atr.PushK(kline) @@ -485,7 +485,7 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine, count return } // for doing the trailing stoploss during backtesting - atr := s.atr.Last() + atr := s.atr.Last(0) price := s.getLastPrice() pricef := price.Float64() @@ -538,7 +538,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter s.drift.Update(sourcef, kline.Volume.Abs().Float64()) s.atr.PushK(kline) - atr := s.atr.Last() + atr := s.atr.Last(0) price := kline.Close // s.getLastPrice() pricef := price.Float64() @@ -563,7 +563,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter return } - log.Infof("highdiff: %3.2f open: %8v, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(), kline.Open, kline.Close, kline.High, kline.Low, kline.StartTime, kline.EndTime) + log.Infof("highdiff: %3.2f open: %8v, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(0), kline.Open, kline.Close, kline.High, kline.Low, kline.StartTime, kline.EndTime) s.positionLock.Lock() if s.lowestPrice > 0 && lowf < s.lowestPrice { @@ -596,7 +596,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter shortCondition := drift[1] >= 0 && drift[0] <= 0 || (drift[1] >= drift[0] && drift[1] <= 0) || ddrift[1] >= 0 && ddrift[0] <= 0 || (ddrift[1] >= ddrift[0] && ddrift[1] <= 0) longCondition := drift[1] <= 0 && drift[0] >= 0 || (drift[1] <= drift[0] && drift[1] >= 0) || ddrift[1] <= 0 && ddrift[0] >= 0 || (ddrift[1] <= ddrift[0] && ddrift[1] >= 0) if shortCondition && longCondition { - if s.priceLines.Index(1) > s.priceLines.Last() { + if s.priceLines.Index(1) > s.priceLines.Last(0) { longCondition = false } else { shortCondition = false @@ -625,7 +625,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter } if longCondition { - source = source.Sub(fixedpoint.NewFromFloat(s.stdevLow.Last() * s.HighLowVarianceMultiplier)) + source = source.Sub(fixedpoint.NewFromFloat(s.stdevLow.Last(0) * s.HighLowVarianceMultiplier)) if source.Compare(price) > 0 { source = price } @@ -654,7 +654,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter return } - log.Infof("source in long %v %v %f", source, price, s.stdevLow.Last()) + log.Infof("source in long %v %v %f", source, price, s.stdevLow.Last(0)) o, err := s.SubmitOrder(ctx, *submitOrder) if err != nil { @@ -675,12 +675,12 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter return } if shortCondition { - source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last() * s.HighLowVarianceMultiplier)) + source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last(0) * s.HighLowVarianceMultiplier)) if source.Compare(price) < 0 { source = price } - log.Infof("source in short: %v %v %f", source, price, s.stdevLow.Last()) + log.Infof("source in short: %v %v %f", source, price, s.stdevLow.Last(0)) opt := s.OpenPositionOptions opt.Short = true diff --git a/pkg/strategy/elliottwave/ewo.go b/pkg/strategy/elliottwave/ewo.go index ff6b7cf2ec..f86706b183 100644 --- a/pkg/strategy/elliottwave/ewo.go +++ b/pkg/strategy/elliottwave/ewo.go @@ -11,8 +11,8 @@ func (s *ElliottWave) Index(i int) float64 { return s.maQuick.Index(i)/s.maSlow.Index(i) - 1.0 } -func (s *ElliottWave) Last() float64 { - return s.maQuick.Last()/s.maSlow.Last() - 1.0 +func (s *ElliottWave) Last(int) float64 { + return s.maQuick.Last(0)/s.maSlow.Last(0) - 1.0 } func (s *ElliottWave) Length() int { diff --git a/pkg/strategy/elliottwave/strategy.go b/pkg/strategy/elliottwave/strategy.go index feabe13b55..c307f054c0 100644 --- a/pkg/strategy/elliottwave/strategy.go +++ b/pkg/strategy/elliottwave/strategy.go @@ -199,12 +199,12 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef float64) int { if s.counter-s.orderPendingCounter[order.OrderID] >= s.PendingMinInterval { toCancel = true } else if order.Side == types.SideTypeBuy { - if order.Price.Float64()+s.atr.Last()*2 <= pricef { + if order.Price.Float64()+s.atr.Last(0)*2 <= pricef { toCancel = true } } else if order.Side == types.SideTypeSell { // 75% of the probability - if order.Price.Float64()-s.atr.Last()*2 >= pricef { + if order.Price.Float64()-s.atr.Last(0)*2 >= pricef { toCancel = true } } else { @@ -425,7 +425,7 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine) { stoploss := s.Stoploss.Float64() price := s.getLastPrice() pricef := price.Float64() - atr := s.atr.Last() + atr := s.atr.Last(0) numPending := s.smartCancel(ctx, pricef) if numPending > 0 { @@ -476,7 +476,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { s.smartCancel(ctx, pricef) - atr := s.atr.Last() + atr := s.atr.Last(0) ewo := types.Array(s.ewo, 4) if len(ewo) < 4 { return diff --git a/pkg/strategy/emastop/strategy.go b/pkg/strategy/emastop/strategy.go index 2326f5f78c..d87ff5d445 100644 --- a/pkg/strategy/emastop/strategy.go +++ b/pkg/strategy/emastop/strategy.go @@ -93,7 +93,7 @@ func (s *Strategy) clear(ctx context.Context, orderExecutor bbgo.OrderExecutor) func (s *Strategy) place(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession, indicator types.Float64Indicator, closePrice fixedpoint.Value) { closePriceF := closePrice.Float64() - movingAveragePriceF := indicator.Last() + movingAveragePriceF := indicator.Last(0) // skip it if it's near zero because it's not loaded yet if movingAveragePriceF < 0.0001 { diff --git a/pkg/strategy/ewoDgtrd/heikinashi.go b/pkg/strategy/ewoDgtrd/heikinashi.go index fca1934c03..345d8e512c 100644 --- a/pkg/strategy/ewoDgtrd/heikinashi.go +++ b/pkg/strategy/ewoDgtrd/heikinashi.go @@ -27,11 +27,11 @@ func NewHeikinAshi(size int) *HeikinAshi { func (s *HeikinAshi) Print() string { return fmt.Sprintf("Heikin c: %.3f, o: %.3f, h: %.3f, l: %.3f, v: %.3f", - s.Close.Last(), - s.Open.Last(), - s.High.Last(), - s.Low.Last(), - s.Volume.Last()) + s.Close.Last(0), + s.Open.Last(0), + s.High.Last(0), + s.Low.Last(0), + s.Volume.Last(0)) } func (inc *HeikinAshi) Update(kline types.KLine) { @@ -40,7 +40,7 @@ func (inc *HeikinAshi) Update(kline types.KLine) { high := kline.High.Float64() low := kline.Low.Float64() newClose := (open + high + low + cloze) / 4. - newOpen := (inc.Open.Last() + inc.Close.Last()) / 2. + newOpen := (inc.Open.Last(0) + inc.Close.Last(0)) / 2. inc.Close.Update(newClose) inc.Open.Update(newOpen) inc.High.Update(math.Max(math.Max(high, newOpen), newClose)) diff --git a/pkg/strategy/ewoDgtrd/strategy.go b/pkg/strategy/ewoDgtrd/strategy.go index e13b84b0e1..50af7f4bf2 100644 --- a/pkg/strategy/ewoDgtrd/strategy.go +++ b/pkg/strategy/ewoDgtrd/strategy.go @@ -139,7 +139,7 @@ func NewCCISTOCH(i types.Interval, filterHigh, filterLow float64) *CCISTOCH { func (inc *CCISTOCH) Update(cloze float64) { inc.cci.Update(cloze) - inc.stoch.Update(inc.cci.Last(), inc.cci.Last(), inc.cci.Last()) + inc.stoch.Update(inc.cci.Last(0), inc.cci.Last(0), inc.cci.Last(0)) inc.ma.Update(inc.stoch.LastD()) } @@ -180,19 +180,19 @@ type VWEMA struct { V types.UpdatableSeries } -func (inc *VWEMA) Last() float64 { - return inc.PV.Last() / inc.V.Last() +func (inc *VWEMA) Last(int) float64 { + return inc.PV.Last(0) / inc.V.Last(0) } func (inc *VWEMA) Index(i int) float64 { if i >= inc.PV.Length() { return 0 } - vi := inc.V.Index(i) + vi := inc.V.Last(i) if vi == 0 { return 0 } - return inc.PV.Index(i) / vi + return inc.PV.Last(i) / vi } func (inc *VWEMA) Length() int { @@ -262,11 +262,11 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { if s.heikinAshi.Close.Length() == 0 { for _, kline := range window { s.heikinAshi.Update(kline) - s.ccis.Update(getSource(window).Last()) + s.ccis.Update(getSource(window).Last(0)) } } else { s.heikinAshi.Update(window[len(window)-1]) - s.ccis.Update(getSource(window).Last()) + s.ccis.Update(getSource(window).Last(0)) } }) if s.UseEma { @@ -283,7 +283,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { ema34.Update(cloze) } } else { - cloze := getSource(window).Last() + cloze := getSource(window).Last(0) ema5.Update(cloze) ema34.Update(cloze) } @@ -306,7 +306,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { sma34.Update(cloze) } } else { - cloze := getSource(window).Last() + cloze := getSource(window).Last(0) sma5.Update(cloze) sma34.Update(cloze) } @@ -330,14 +330,14 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { vols := getVol(window) if evwma5.PV.Length() == 0 { for i := clozes.Length() - 1; i >= 0; i-- { - price := clozes.Index(i) - vol := vols.Index(i) + price := clozes.Last(i) + vol := vols.Last(i) evwma5.UpdateVal(price, vol) evwma34.UpdateVal(price, vol) } } else { - price := clozes.Last() - vol := vols.Last() + price := clozes.Last(0) + vol := vols.Last(0) evwma5.UpdateVal(price, vol) evwma34.UpdateVal(price, vol) } @@ -363,7 +363,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { sig.Update(ewoValue) } } else { - sig.Update(s.ewo.Last()) + sig.Update(s.ewo.Last(0)) } }) s.ewoSignal = sig @@ -381,7 +381,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { sig.Update(ewoValue) } } else { - sig.Update(s.ewo.Last()) + sig.Update(s.ewo.Last(0)) } }) s.ewoSignal = sig @@ -398,13 +398,13 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) { // lazy init ewoVals := s.ewo.Reverse() for i, ewoValue := range ewoVals { - vol := window.Volume().Index(i) + vol := window.Volume().Last(i) sig.PV.Update(ewoValue * vol) sig.V.Update(vol) } } else { - vol := window.Volume().Last() - sig.PV.Update(s.ewo.Last() * vol) + vol := window.Volume().Last(0) + sig.PV.Update(s.ewo.Last(0) * vol) sig.V.Update(vol) } }) @@ -663,11 +663,13 @@ func (s *Strategy) GetLastPrice() fixedpoint.Value { // - TP by (lastprice < peak price - atr) || (lastprice > bottom price + atr) // - SL by s.StopLoss (Abs(price_diff / price) > s.StopLoss) // - entry condition on ewo(Elliott wave oscillator) Crosses ewoSignal(ma on ewo, signalWindow) -// * buy signal on (crossover on previous K bar and no crossunder on latest K bar) -// * sell signal on (crossunder on previous K bar and no crossunder on latest K bar) +// - buy signal on (crossover on previous K bar and no crossunder on latest K bar) +// - sell signal on (crossunder on previous K bar and no crossunder on latest K bar) +// // - and filtered by the following rules: -// * buy: buy signal ON, kline Close > Open, Close > ma5, Close > ma34, CCI Stochastic Buy signal -// * sell: sell signal ON, kline Close < Open, Close < ma5, Close < ma34, CCI Stochastic Sell signal +// - buy: buy signal ON, kline Close > Open, Close > ma5, Close > ma34, CCI Stochastic Buy signal +// - sell: sell signal ON, kline Close < Open, Close < ma5, Close < ma34, CCI Stochastic Sell signal +// // - or entry when ma34 +- atr * 3 gets touched // - entry price: latestPrice +- atr / 2 (short,long), close at market price // Cancel non-fully filled orders on new signal (either in same direction or not) @@ -784,8 +786,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.SetupIndicators(store) // local peak of ewo - shortSig := s.ewo.Last() < s.ewo.Index(1) && s.ewo.Index(1) > s.ewo.Index(2) - longSig := s.ewo.Last() > s.ewo.Index(1) && s.ewo.Index(1) < s.ewo.Index(2) + shortSig := s.ewo.Last(0) < s.ewo.Last(1) && s.ewo.Last(1) > s.ewo.Last(2) + longSig := s.ewo.Last(0) > s.ewo.Last(1) && s.ewo.Last(1) < s.ewo.Last(2) sellOrderTPSL := func(price fixedpoint.Value) { lastPrice := s.GetLastPrice() @@ -798,8 +800,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } balances := session.GetAccount().Balances() quoteBalance := balances[s.Market.QuoteCurrency].Available - atr := fixedpoint.NewFromFloat(s.atr.Last()) - atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2) + atr := fixedpoint.NewFromFloat(s.atr.Last(0)) + atrx2 := fixedpoint.NewFromFloat(s.atr.Last(0) * 2) buyall := false if s.bottomPrice.IsZero() || s.bottomPrice.Compare(price) > 0 { s.bottomPrice = price @@ -809,7 +811,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se spBack := s.sellPrice reason := -1 if quoteBalance.Div(lastPrice).Compare(s.Market.MinQuantity) >= 0 && quoteBalance.Compare(s.Market.MinNotional) >= 0 { - base := fixedpoint.NewFromFloat(s.ma34.Last()) + base := fixedpoint.NewFromFloat(s.ma34.Last(0)) // TP if lastPrice.Compare(s.sellPrice) < 0 && (longSig || (!atrx2.IsZero() && base.Sub(atrx2).Compare(lastPrice) >= 0)) { @@ -903,8 +905,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } balances := session.GetAccount().Balances() baseBalance := balances[s.Market.BaseCurrency].Available - atr := fixedpoint.NewFromFloat(s.atr.Last()) - atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2) + atr := fixedpoint.NewFromFloat(s.atr.Last(0)) + atrx2 := fixedpoint.NewFromFloat(s.atr.Last(0) * 2) sellall := false if s.peakPrice.IsZero() || s.peakPrice.Compare(price) < 0 { s.peakPrice = price @@ -915,7 +917,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se reason := -1 if baseBalance.Compare(s.Market.MinQuantity) >= 0 && baseBalance.Mul(lastPrice).Compare(s.Market.MinNotional) >= 0 { // TP - base := fixedpoint.NewFromFloat(s.ma34.Last()) + base := fixedpoint.NewFromFloat(s.ma34.Last(0)) if lastPrice.Compare(s.buyPrice) > 0 && (shortSig || (!atrx2.IsZero() && base.Add(atrx2).Compare(lastPrice) <= 0)) { sellall = true @@ -1089,10 +1091,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se balances := session.GetAccount().Balances() baseBalance := balances[s.Market.BaseCurrency].Total() quoteBalance := balances[s.Market.QuoteCurrency].Total() - atr := fixedpoint.NewFromFloat(s.atr.Last()) + atr := fixedpoint.NewFromFloat(s.atr.Last(0)) if !s.Environment.IsBackTesting() { log.Infof("Get last price: %v, ewo %f, ewoSig %f, ccis: %f, atr %v, kline: %v, balance[base]: %v balance[quote]: %v", - lastPrice, s.ewo.Last(), s.ewoSignal.Last(), s.ccis.ma.Last(), atr, kline, baseBalance, quoteBalance) + lastPrice, s.ewo.Last(0), s.ewoSignal.Last(0), s.ccis.ma.Last(0), atr, kline, baseBalance, quoteBalance) } if kline.Interval != s.Interval { @@ -1104,21 +1106,21 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se priceChangeRate := (priceHighest - priceLowest) / priceHighest / 14 ewoHighest := types.Highest(s.ewoHistogram, 233) - s.ewoChangeRate = math.Abs(s.ewoHistogram.Last() / ewoHighest * priceChangeRate) + s.ewoChangeRate = math.Abs(s.ewoHistogram.Last(0) / ewoHighest * priceChangeRate) longSignal := types.CrossOver(s.ewo, s.ewoSignal) shortSignal := types.CrossUnder(s.ewo, s.ewoSignal) - base := s.ma34.Last() - sellLine := base + s.atr.Last()*3 - buyLine := base - s.atr.Last()*3 + base := s.ma34.Last(0) + sellLine := base + s.atr.Last(0)*3 + buyLine := base - s.atr.Last(0)*3 clozes := getClose(window) opens := getOpen(window) // get trend flags - bull := clozes.Last() > opens.Last() - breakThrough := clozes.Last() > s.ma5.Last() && clozes.Last() > s.ma34.Last() - breakDown := clozes.Last() < s.ma5.Last() && clozes.Last() < s.ma34.Last() + bull := clozes.Last(0) > opens.Last(0) + breakThrough := clozes.Last(0) > s.ma5.Last(0) && clozes.Last(0) > s.ma34.Last(0) + breakDown := clozes.Last(0) < s.ma5.Last(0) && clozes.Last(0) < s.ma34.Last(0) // kline breakthrough ma5, ma34 trend up, and cci Stochastic bull IsBull := bull && breakThrough && s.ccis.BuySignal() && s.ewoChangeRate < s.EwoChangeFilterHigh && s.ewoChangeRate > s.EwoChangeFilterLow @@ -1141,7 +1143,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // backup, since the s.sellPrice will be cleared when doing ClosePosition sellPrice := s.sellPrice - log.Errorf("ewoChangeRate %v, emv %v", s.ewoChangeRate, s.emv.Last()) + log.Errorf("ewoChangeRate %v, emv %v", s.ewoChangeRate, s.emv.Last(0)) // calculate report if closeOrder, _ := s.PlaceBuyOrder(ctx, price); closeOrder != nil { @@ -1175,7 +1177,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // backup, since the s.buyPrice will be cleared when doing ClosePosition buyPrice := s.buyPrice - log.Errorf("ewoChangeRate: %v, emv %v", s.ewoChangeRate, s.emv.Last()) + log.Errorf("ewoChangeRate: %v, emv %v", s.ewoChangeRate, s.emv.Last(0)) // calculate report if closeOrder, _ := s.PlaceSellOrder(ctx, price); closeOrder != nil { diff --git a/pkg/strategy/factorzoo/factors/momentum.go b/pkg/strategy/factorzoo/factors/momentum.go index c9dc34877b..827af09760 100644 --- a/pkg/strategy/factorzoo/factors/momentum.go +++ b/pkg/strategy/factorzoo/factors/momentum.go @@ -34,14 +34,14 @@ func (inc *MOM) Index(i int) float64 { if inc.Values == nil { return 0 } - return inc.Values.Index(i) + return inc.Values.Last(i) } -func (inc *MOM) Last() float64 { +func (inc *MOM) Last(int) float64 { if inc.Values.Length() == 0 { return 0 } - return inc.Values.Last() + return inc.Values.Last(0) } func (inc *MOM) Length() int { @@ -51,7 +51,7 @@ func (inc *MOM) Length() int { return inc.Values.Length() } -//var _ types.SeriesExtend = &MOM{} +// var _ types.SeriesExtend = &MOM{} func (inc *MOM) Update(open, close float64) { if inc.SeriesBase.Series == nil { @@ -62,7 +62,7 @@ func (inc *MOM) Update(open, close float64) { inc.opens.Update(open) inc.closes.Update(close) if inc.opens.Length() >= inc.Window && inc.closes.Length() >= inc.Window { - gap := inc.opens.Last()/inc.closes.Index(1) - 1 + gap := inc.opens.Last(0)/inc.closes.Index(1) - 1 inc.Values.Push(gap) } } @@ -72,11 +72,11 @@ func (inc *MOM) CalculateAndUpdate(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } @@ -99,7 +99,7 @@ func (inc *MOM) PushK(k types.KLine) { inc.Update(k.Open.Float64(), k.Close.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func calculateMomentum(klines []types.KLine, window int, valA KLineValueMapper, valB KLineValueMapper) (float64, error) { diff --git a/pkg/strategy/factorzoo/factors/price_mean_reversion.go b/pkg/strategy/factorzoo/factors/price_mean_reversion.go index 2d2bd5ce89..7143e547cb 100644 --- a/pkg/strategy/factorzoo/factors/price_mean_reversion.go +++ b/pkg/strategy/factorzoo/factors/price_mean_reversion.go @@ -35,12 +35,12 @@ func (inc *PMR) Update(price float64) { } inc.SMA.Update(price) if inc.SMA.Length() >= inc.Window { - reversion := inc.SMA.Last() / price + reversion := inc.SMA.Last(0) / price inc.Values.Push(reversion) } } -func (inc *PMR) Last() float64 { +func (inc *PMR) Last(int) float64 { if len(inc.Values) == 0 { return 0 } @@ -65,11 +65,11 @@ func (inc *PMR) CalculateAndUpdate(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } @@ -92,7 +92,7 @@ func (inc *PMR) PushK(k types.KLine) { inc.Update(indicator.KLineClosePriceMapper(k)) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func CalculateKLinesPMR(allKLines []types.KLine, window int) float64 { diff --git a/pkg/strategy/factorzoo/factors/price_volume_divergence.go b/pkg/strategy/factorzoo/factors/price_volume_divergence.go index 54b07a2659..adc20e02a3 100644 --- a/pkg/strategy/factorzoo/factors/price_volume_divergence.go +++ b/pkg/strategy/factorzoo/factors/price_volume_divergence.go @@ -47,7 +47,7 @@ func (inc *PVD) Update(price float64, volume float64) { } } -func (inc *PVD) Last() float64 { +func (inc *PVD) Last(int) float64 { if len(inc.Values) == 0 { return 0 } @@ -72,11 +72,11 @@ func (inc *PVD) CalculateAndUpdate(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } @@ -99,7 +99,7 @@ func (inc *PVD) PushK(k types.KLine) { inc.Update(indicator.KLineClosePriceMapper(k), indicator.KLineVolumeMapper(k)) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func CalculateKLinesPVD(allKLines []types.KLine, window int) float64 { diff --git a/pkg/strategy/factorzoo/factors/return_rate.go b/pkg/strategy/factorzoo/factors/return_rate.go index 057f2ac5c2..3fe3d41565 100644 --- a/pkg/strategy/factorzoo/factors/return_rate.go +++ b/pkg/strategy/factorzoo/factors/return_rate.go @@ -30,12 +30,12 @@ func (inc *RR) Update(price float64) { inc.prices = types.NewQueue(inc.Window) } inc.prices.Update(price) - irr := inc.prices.Last()/inc.prices.Index(1) - 1 + irr := inc.prices.Last(0)/inc.prices.Index(1) - 1 inc.Values.Push(irr) } -func (inc *RR) Last() float64 { +func (inc *RR) Last(int) float64 { if len(inc.Values) == 0 { return 0 } @@ -60,11 +60,11 @@ func (inc *RR) CalculateAndUpdate(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } @@ -91,14 +91,14 @@ func (inc *RR) PushK(k types.KLine) { inc.Update(indicator.KLineClosePriceMapper(k)) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func (inc *RR) LoadK(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } //func calculateReturn(klines []types.KLine, window int, val KLineValueMapper) (float64, error) { diff --git a/pkg/strategy/factorzoo/factors/volume_momentum.go b/pkg/strategy/factorzoo/factors/volume_momentum.go index 602065273c..87029ed9ed 100644 --- a/pkg/strategy/factorzoo/factors/volume_momentum.go +++ b/pkg/strategy/factorzoo/factors/volume_momentum.go @@ -33,14 +33,14 @@ func (inc *VMOM) Index(i int) float64 { if inc.Values == nil { return 0 } - return inc.Values.Index(i) + return inc.Values.Last(i) } -func (inc *VMOM) Last() float64 { +func (inc *VMOM) Last(int) float64 { if inc.Values.Length() == 0 { return 0 } - return inc.Values.Last() + return inc.Values.Last(0) } func (inc *VMOM) Length() int { @@ -59,7 +59,7 @@ func (inc *VMOM) Update(volume float64) { } inc.volumes.Update(volume) if inc.volumes.Length() >= inc.Window { - v := inc.volumes.Last() / inc.volumes.Mean() + v := inc.volumes.Last(0) / inc.volumes.Mean() inc.Values.Push(v) } } @@ -69,11 +69,11 @@ func (inc *VMOM) CalculateAndUpdate(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } else { k := allKLines[len(allKLines)-1] inc.PushK(k) - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } } @@ -96,7 +96,7 @@ func (inc *VMOM) PushK(k types.KLine) { inc.Update(k.Volume.Float64()) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func calculateVolumeMomentum(klines []types.KLine, window int, valV KLineValueMapper, valP KLineValueMapper) (float64, error) { @@ -110,7 +110,7 @@ func calculateVolumeMomentum(klines []types.KLine, window int, valV KLineValueMa vma += valV(p) } vma /= float64(window) - momentum := valV(klines[length-1]) / vma //* (valP(klines[length-1-2]) / valP(klines[length-1])) + momentum := valV(klines[length-1]) / vma // * (valP(klines[length-1-2]) / valP(klines[length-1])) return momentum, nil } diff --git a/pkg/strategy/factorzoo/linear_regression.go b/pkg/strategy/factorzoo/linear_regression.go index 80c84e6870..670e4dcea8 100644 --- a/pkg/strategy/factorzoo/linear_regression.go +++ b/pkg/strategy/factorzoo/linear_regression.go @@ -104,11 +104,11 @@ func (s *Linear) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.General // use the last value from indicators, or the SeriesExtends' predict function. (e.g., look back: 5) input := []float64{ - s.divergence.Last(), - s.reversion.Last(), - s.drift.Last(), - s.momentum.Last(), - s.volume.Last(), + s.divergence.Last(0), + s.reversion.Last(0), + s.drift.Last(0), + s.momentum.Last(0), + s.volume.Last(0), } pred := model.Predict(input) predLst.Update(pred) diff --git a/pkg/strategy/fixedmaker/strategy.go b/pkg/strategy/fixedmaker/strategy.go index d316334062..0910e536d6 100644 --- a/pkg/strategy/fixedmaker/strategy.go +++ b/pkg/strategy/fixedmaker/strategy.go @@ -213,7 +213,7 @@ func (s *Strategy) generateSubmitOrders(ctx context.Context) ([]types.SubmitOrde log.Infof("mid price: %+v", midPrice) if s.ATRMultiplier.Float64() > 0 { - atr := fixedpoint.NewFromFloat(s.atr.Last()) + atr := fixedpoint.NewFromFloat(s.atr.Last(0)) log.Infof("atr: %s", atr.String()) s.HalfSpreadRatio = s.ATRMultiplier.Mul(atr).Div(midPrice) log.Infof("half spread ratio: %s", s.HalfSpreadRatio.String()) diff --git a/pkg/strategy/flashcrash/strategy.go b/pkg/strategy/flashcrash/strategy.go index aff86f3ca5..d4db389254 100644 --- a/pkg/strategy/flashcrash/strategy.go +++ b/pkg/strategy/flashcrash/strategy.go @@ -75,7 +75,7 @@ func (s *Strategy) updateBidOrders(orderExecutor bbgo.OrderExecutor, session *bb return } - var startPrice = fixedpoint.NewFromFloat(s.ewma.Last()).Mul(s.Percentage) + var startPrice = fixedpoint.NewFromFloat(s.ewma.Last(0)).Mul(s.Percentage) var submitOrders []types.SubmitOrder for i := 0; i < s.GridNum; i++ { diff --git a/pkg/strategy/fmaker/A18.go b/pkg/strategy/fmaker/A18.go index 6e7e603a00..35dc7368e4 100644 --- a/pkg/strategy/fmaker/A18.go +++ b/pkg/strategy/fmaker/A18.go @@ -21,7 +21,7 @@ type A18 struct { UpdateCallbacks []func(val float64) } -func (inc *A18) Last() float64 { +func (inc *A18) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } @@ -84,8 +84,8 @@ func calculateA18(klines []types.KLine, valClose KLineValueMapper) (float64, err closes.Push(valClose(k)) } - delay5 := closes.Index(4) - curr := closes.Index(0) + delay5 := closes.Last(4) + curr := closes.Last(0) alpha := curr / delay5 return alpha, nil diff --git a/pkg/strategy/fmaker/A2.go b/pkg/strategy/fmaker/A2.go index 0c9cc4b3bf..cf674e1a6b 100644 --- a/pkg/strategy/fmaker/A2.go +++ b/pkg/strategy/fmaker/A2.go @@ -21,7 +21,7 @@ type A2 struct { UpdateCallbacks []func(val float64) } -func (inc *A2) Last() float64 { +func (inc *A2) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } @@ -88,8 +88,8 @@ func calculateA2(klines []types.KLine, valLow KLineValueMapper, valHigh KLineVal closes.Push(valClose(k)) } - prev := ((closes.Index(1) - lows.Index(1)) - (highs.Index(1) - closes.Index(1))) / (highs.Index(1) - lows.Index(1)) - curr := ((closes.Index(0) - lows.Index(0)) - (highs.Index(0) - closes.Index(0))) / (highs.Index(0) - lows.Index(0)) + prev := ((closes.Last(1) - lows.Index(1)) - (highs.Index(1) - closes.Index(1))) / (highs.Index(1) - lows.Index(1)) + curr := ((closes.Last(0) - lows.Index(0)) - (highs.Index(0) - closes.Index(0))) / (highs.Index(0) - lows.Index(0)) alpha := (curr - prev) * -1 // delta(1 interval) return alpha, nil diff --git a/pkg/strategy/fmaker/A3.go b/pkg/strategy/fmaker/A3.go index 945b06b670..02118def87 100644 --- a/pkg/strategy/fmaker/A3.go +++ b/pkg/strategy/fmaker/A3.go @@ -22,7 +22,7 @@ type A3 struct { UpdateCallbacks []func(val float64) } -func (inc *A3) Last() float64 { +func (inc *A3) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } diff --git a/pkg/strategy/fmaker/A34.go b/pkg/strategy/fmaker/A34.go index eb01799e3e..ba60aeedd1 100644 --- a/pkg/strategy/fmaker/A34.go +++ b/pkg/strategy/fmaker/A34.go @@ -21,7 +21,7 @@ type A34 struct { UpdateCallbacks []func(val float64) } -func (inc *A34) Last() float64 { +func (inc *A34) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } @@ -83,7 +83,7 @@ func calculateA34(klines []types.KLine, valClose KLineValueMapper) (float64, err closes.Push(valClose(k)) } - c := closes.Last() + c := closes.Last(0) sumC := 0. for i := 1; i <= 12; i++ { diff --git a/pkg/strategy/fmaker/R.go b/pkg/strategy/fmaker/R.go index d3aa0eca21..dc4f0eabfe 100644 --- a/pkg/strategy/fmaker/R.go +++ b/pkg/strategy/fmaker/R.go @@ -25,7 +25,7 @@ type R struct { UpdateCallbacks []func(val float64) } -func (inc *R) Last() float64 { +func (inc *R) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } diff --git a/pkg/strategy/fmaker/S0.go b/pkg/strategy/fmaker/S0.go index 9c934ccd2c..7d2eda1589 100644 --- a/pkg/strategy/fmaker/S0.go +++ b/pkg/strategy/fmaker/S0.go @@ -21,7 +21,7 @@ type S0 struct { UpdateCallbacks []func(val float64) } -func (inc *S0) Last() float64 { +func (inc *S0) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } @@ -84,7 +84,7 @@ func calculateS0(klines []types.KLine, valClose KLineValueMapper) (float64, erro } sma := floats.Slice.Sum(closes[len(closes)-window:len(closes)-1]) / float64(window) - alpha := sma / closes.Last() + alpha := sma / closes.Last(0) return alpha, nil } diff --git a/pkg/strategy/fmaker/S1.go b/pkg/strategy/fmaker/S1.go index 498efec63e..68962a579e 100644 --- a/pkg/strategy/fmaker/S1.go +++ b/pkg/strategy/fmaker/S1.go @@ -19,7 +19,7 @@ type S1 struct { UpdateCallbacks []func(value float64) } -func (inc *S1) Last() float64 { +func (inc *S1) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } diff --git a/pkg/strategy/fmaker/S2.go b/pkg/strategy/fmaker/S2.go index 960b3c5a83..d63488a985 100644 --- a/pkg/strategy/fmaker/S2.go +++ b/pkg/strategy/fmaker/S2.go @@ -19,7 +19,7 @@ type S2 struct { UpdateCallbacks []func(value float64) } -func (inc *S2) Last() float64 { +func (inc *S2) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } diff --git a/pkg/strategy/fmaker/S3.go b/pkg/strategy/fmaker/S3.go index 238cc62ea3..e00f83625e 100644 --- a/pkg/strategy/fmaker/S3.go +++ b/pkg/strategy/fmaker/S3.go @@ -21,7 +21,7 @@ type S3 struct { UpdateCallbacks []func(val float64) } -func (inc *S3) Last() float64 { +func (inc *S3) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } diff --git a/pkg/strategy/fmaker/S4.go b/pkg/strategy/fmaker/S4.go index 9d122c4734..b7f8cfd8ce 100644 --- a/pkg/strategy/fmaker/S4.go +++ b/pkg/strategy/fmaker/S4.go @@ -21,7 +21,7 @@ type S4 struct { UpdateCallbacks []func(val float64) } -func (inc *S4) Last() float64 { +func (inc *S4) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } diff --git a/pkg/strategy/fmaker/S5.go b/pkg/strategy/fmaker/S5.go index 046733b4f8..8fd2edfb75 100644 --- a/pkg/strategy/fmaker/S5.go +++ b/pkg/strategy/fmaker/S5.go @@ -21,7 +21,7 @@ type S5 struct { UpdateCallbacks []func(val float64) } -func (inc *S5) Last() float64 { +func (inc *S5) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } @@ -83,7 +83,7 @@ func calculateS5(klines []types.KLine, valVolume KLineValueMapper) (float64, err volumes.Push(valVolume(k)) } - v := volumes.Last() + v := volumes.Last(0) sumV := 0. for i := 1; i <= 10; i++ { diff --git a/pkg/strategy/fmaker/S6.go b/pkg/strategy/fmaker/S6.go index 4bb20b158d..8c7f73c16a 100644 --- a/pkg/strategy/fmaker/S6.go +++ b/pkg/strategy/fmaker/S6.go @@ -21,7 +21,7 @@ type S6 struct { UpdateCallbacks []func(val float64) } -func (inc *S6) Last() float64 { +func (inc *S6) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } @@ -90,10 +90,10 @@ func calculateS6(klines []types.KLine, valHigh KLineValueMapper, valLow KLineVal } - H := highs.Last() - L := lows.Last() - C := closes.Last() - V := volumes.Last() + H := highs.Last(0) + L := lows.Last(0) + C := closes.Last(0) + V := volumes.Last(0) alpha := (H + L + C) / 3 * V return alpha, nil diff --git a/pkg/strategy/fmaker/S7.go b/pkg/strategy/fmaker/S7.go index 7000e6897f..048078fe27 100644 --- a/pkg/strategy/fmaker/S7.go +++ b/pkg/strategy/fmaker/S7.go @@ -21,7 +21,7 @@ type S7 struct { UpdateCallbacks []func(val float64) } -func (inc *S7) Last() float64 { +func (inc *S7) Last(int) float64 { if len(inc.Values) == 0 { return 0.0 } @@ -86,8 +86,8 @@ func calculateS7(klines []types.KLine, valOpen KLineValueMapper, valClose KLineV } - O := opens.Last() - C := closes.Last() + O := opens.Last(0) + C := closes.Last(0) alpha := -(1 - O/C) return alpha, nil diff --git a/pkg/strategy/fmaker/strategy.go b/pkg/strategy/fmaker/strategy.go index 0df9b68e20..d03b9bf37a 100644 --- a/pkg/strategy/fmaker/strategy.go +++ b/pkg/strategy/fmaker/strategy.go @@ -341,7 +341,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // } r.Train(rdps...) r.Run() - er, _ := r.Predict(floats2.Slice{s.S0.Last(), s.S1.Last(), s.S2.Last(), s.S4.Last(), s.S5.Last(), s.S6.Last(), s.S7.Last(), s.A2.Last(), s.A3.Last(), s.A18.Last(), s.A34.Last()}) + er, _ := r.Predict(floats2.Slice{s.S0.Last(0), s.S1.Last(0), s.S2.Last(0), s.S4.Last(0), s.S5.Last(0), s.S6.Last(0), s.S7.Last(0), s.A2.Last(0), s.A3.Last(0), s.A18.Last(0), s.A34.Last(0)}) log.Infof("Expected Return Rate: %f", er) q := new(regression.Regression) @@ -399,7 +399,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se q.Run() - log.Info(s.S0.Last(), s.S1.Last(), s.S2.Last(), s.S3.Last(), s.S4.Last(), s.S5.Last(), s.S6.Last(), s.S7.Last(), s.A2.Last(), s.A3.Last(), s.A18.Last(), s.A34.Last()) + log.Info(s.S0.Last(0), s.S1.Last(0), s.S2.Last(0), s.S3.Last(0), s.S4.Last(0), s.S5.Last(0), s.S6.Last(0), s.S7.Last(0), s.A2.Last(0), s.A3.Last(0), s.A18.Last(0), s.A34.Last(0)) log.Infof("Return Rate Regression formula:\n%v", r.Formula) log.Infof("Order Quantity Regression formula:\n%v", q.Formula) @@ -416,7 +416,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // a34 := preprocessing(s.A18.Values[len(s.A18.Values)-20 : len(s.A18.Values)-1-outlook]) // er, _ := r.Predict(types.Float64Slice{s0, s1, s2, s4, s5, a2, a3, a18, a34}) // eq, _ := q.Predict(types.Float64Slice{s0, s1, s2, s4, s5, a2, a3, a18, a34}) - eq, _ := q.Predict(floats2.Slice{s.S0.Last(), s.S1.Last(), s.S2.Last(), s.S4.Last(), s.S5.Last(), s.S6.Last(), s.S7.Last(), s.A2.Last(), s.A3.Last(), s.A18.Last(), s.A34.Last(), er}) + eq, _ := q.Predict(floats2.Slice{s.S0.Last(0), s.S1.Last(0), s.S2.Last(0), s.S4.Last(0), s.S5.Last(0), s.S6.Last(0), s.S7.Last(0), s.A2.Last(0), s.A3.Last(0), s.A18.Last(0), s.A34.Last(0), er}) log.Infof("Expected Order Quantity: %f", eq) // if float64(s.Position.GetBase().Sign())*er < 0 { // s.ClosePosition(ctx, fixedpoint.One, kline.Close) diff --git a/pkg/strategy/grid2/strategy.go b/pkg/strategy/grid2/strategy.go index b8a614e17c..dc45dffd04 100644 --- a/pkg/strategy/grid2/strategy.go +++ b/pkg/strategy/grid2/strategy.go @@ -1849,8 +1849,8 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo. interval := s.AutoRange.Interval() pivotLow := indicatorSet.PivotLow(types.IntervalWindow{Interval: interval, Window: s.AutoRange.Num}) pivotHigh := indicatorSet.PivotHigh(types.IntervalWindow{Interval: interval, Window: s.AutoRange.Num}) - s.UpperPrice = fixedpoint.NewFromFloat(pivotHigh.Last()) - s.LowerPrice = fixedpoint.NewFromFloat(pivotLow.Last()) + s.UpperPrice = fixedpoint.NewFromFloat(pivotHigh.Last(0)) + s.LowerPrice = fixedpoint.NewFromFloat(pivotLow.Last(0)) s.logger.Infof("autoRange is enabled, using pivot high %f and pivot low %f", s.UpperPrice.Float64(), s.LowerPrice.Float64()) } diff --git a/pkg/strategy/harmonic/shark.go b/pkg/strategy/harmonic/shark.go index 630d4f4bf7..d7bc287c38 100644 --- a/pkg/strategy/harmonic/shark.go +++ b/pkg/strategy/harmonic/shark.go @@ -51,7 +51,7 @@ func (inc *SHARK) Update(high, low, price float64) { } -func (inc *SHARK) Last() float64 { +func (inc *SHARK) Last(int) float64 { if len(inc.Values) == 0 { return 0 } @@ -82,14 +82,14 @@ func (inc *SHARK) PushK(k types.KLine) { inc.Update(indicator.KLineHighPriceMapper(k), indicator.KLineLowPriceMapper(k), indicator.KLineClosePriceMapper(k)) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func (inc *SHARK) LoadK(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func (inc SHARK) SharkLong(highs, lows floats.Slice, p float64, lookback int) float64 { diff --git a/pkg/strategy/harmonic/strategy.go b/pkg/strategy/harmonic/strategy.go index 4d98fda34a..c73a3d7363 100644 --- a/pkg/strategy/harmonic/strategy.go +++ b/pkg/strategy/harmonic/strategy.go @@ -145,7 +145,7 @@ func (r *AccumulatedProfitReport) DailyUpdate(tradeStats *types.TradeStats) { // Accumulated profit MA r.accumulatedProfitMA.Update(r.accumulatedProfit.Float64()) - r.accumulatedProfitMAPerDay.Update(r.accumulatedProfitMA.Last()) + r.accumulatedProfitMAPerDay.Update(r.accumulatedProfitMA.Last(0)) // Accumulated Fee r.accumulatedFeePerDay.Update(r.accumulatedFee.Float64()) @@ -341,11 +341,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se states.Update(0) s.session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { - log.Infof("shark score: %f, current price: %f", s.shark.Last(), kline.Close.Float64()) + log.Infof("shark score: %f, current price: %f", s.shark.Last(0), kline.Close.Float64()) nextState := hmm(s.shark.Array(s.Window), states.Array(s.Window), s.Window) states.Update(nextState) - log.Infof("Denoised signal via HMM: %f", states.Last()) + log.Infof("Denoised signal via HMM: %f", states.Last(0)) if states.Length() < s.Window { return diff --git a/pkg/strategy/irr/neg_return_rate.go b/pkg/strategy/irr/neg_return_rate.go index 473f2ecddc..72fe420feb 100644 --- a/pkg/strategy/irr/neg_return_rate.go +++ b/pkg/strategy/irr/neg_return_rate.go @@ -44,16 +44,16 @@ func (inc *NRR) Update(openPrice, closePrice float64) { irr := (closePrice - openPrice) / openPrice if inc.prices.Length() >= inc.Window && inc.delay { // D1 - nirr = -1 * ((inc.prices.Last() / inc.prices.Index(inc.Window-1)) - 1) - irr = (inc.prices.Last() / inc.prices.Index(inc.Window-1)) - 1 + nirr = -1 * ((inc.prices.Last(0) / inc.prices.Index(inc.Window-1)) - 1) + irr = (inc.prices.Last(0) / inc.prices.Index(inc.Window-1)) - 1 } - inc.Values.Push(nirr) // neg ret here - inc.RankedValues.Push(inc.Rank(inc.RankingWindow).Last() / float64(inc.RankingWindow)) // ranked neg ret here + inc.Values.Push(nirr) // neg ret here + inc.RankedValues.Push(inc.Rank(inc.RankingWindow).Last(0) / float64(inc.RankingWindow)) // ranked neg ret here inc.ReturnValues.Push(irr) } -func (inc *NRR) Last() float64 { +func (inc *NRR) Last(int) float64 { if len(inc.Values) == 0 { return 0 } @@ -84,12 +84,12 @@ func (inc *NRR) PushK(k types.KLine) { inc.Update(indicator.KLineOpenPriceMapper(k), indicator.KLineClosePriceMapper(k)) inc.EndTime = k.EndTime.Time() - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } func (inc *NRR) LoadK(allKLines []types.KLine) { for _, k := range allKLines { inc.PushK(k) } - inc.EmitUpdate(inc.Last()) + inc.EmitUpdate(inc.Last(0)) } diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 679c50ef52..0e7616c951 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -152,7 +152,7 @@ func (r *AccumulatedProfitReport) DailyUpdate(tradeStats *types.TradeStats) { // Accumulated profit MA r.accumulatedProfitMA.Update(r.accumulatedProfit.Float64()) - r.accumulatedProfitMAPerDay.Update(r.accumulatedProfitMA.Last()) + r.accumulatedProfitMAPerDay.Update(r.accumulatedProfitMA.Last(0)) // Accumulated Fee r.accumulatedFeePerDay.Update(r.accumulatedFee.Float64()) diff --git a/pkg/strategy/linregmaker/strategy.go b/pkg/strategy/linregmaker/strategy.go index 34ff4fb2d8..b8e5f9be05 100644 --- a/pkg/strategy/linregmaker/strategy.go +++ b/pkg/strategy/linregmaker/strategy.go @@ -257,12 +257,12 @@ func (s *Strategy) isAllowOppositePosition() bool { return false } - if (s.mainTrendCurrent == types.DirectionUp && s.FastLinReg.Last() < 0 && s.SlowLinReg.Last() < 0) || - (s.mainTrendCurrent == types.DirectionDown && s.FastLinReg.Last() > 0 && s.SlowLinReg.Last() > 0) { - log.Infof("%s allow opposite position is enabled: MainTrend %v, FastLinReg: %f, SlowLinReg: %f", s.Symbol, s.mainTrendCurrent, s.FastLinReg.Last(), s.SlowLinReg.Last()) + if (s.mainTrendCurrent == types.DirectionUp && s.FastLinReg.Last(0) < 0 && s.SlowLinReg.Last(0) < 0) || + (s.mainTrendCurrent == types.DirectionDown && s.FastLinReg.Last(0) > 0 && s.SlowLinReg.Last(0) > 0) { + log.Infof("%s allow opposite position is enabled: MainTrend %v, FastLinReg: %f, SlowLinReg: %f", s.Symbol, s.mainTrendCurrent, s.FastLinReg.Last(0), s.SlowLinReg.Last(0)) return true } - log.Infof("%s allow opposite position is disabled: MainTrend %v, FastLinReg: %f, SlowLinReg: %f", s.Symbol, s.mainTrendCurrent, s.FastLinReg.Last(), s.SlowLinReg.Last()) + log.Infof("%s allow opposite position is disabled: MainTrend %v, FastLinReg: %f, SlowLinReg: %f", s.Symbol, s.mainTrendCurrent, s.FastLinReg.Last(0), s.SlowLinReg.Last(0)) return false } @@ -390,10 +390,10 @@ func (s *Strategy) getOrderQuantities(askPrice fixedpoint.Value, bidPrice fixedp } // Faster position decrease - if s.mainTrendCurrent == types.DirectionUp && s.SlowLinReg.Last() < 0 { + if s.mainTrendCurrent == types.DirectionUp && s.SlowLinReg.Last(0) < 0 { sellQuantity = sellQuantity.Mul(s.FasterDecreaseRatio) log.Infof("faster %s position decrease: sell qty %v", s.Symbol, sellQuantity) - } else if s.mainTrendCurrent == types.DirectionDown && s.SlowLinReg.Last() > 0 { + } else if s.mainTrendCurrent == types.DirectionDown && s.SlowLinReg.Last(0) > 0 { buyQuantity = buyQuantity.Mul(s.FasterDecreaseRatio) log.Infof("faster %s position decrease: buy qty %v", s.Symbol, buyQuantity) } @@ -475,12 +475,12 @@ func (s *Strategy) getCanBuySell(buyQuantity, bidPrice, sellQuantity, askPrice, // Check TradeInBand if s.TradeInBand { // Price too high - if bidPrice.Float64() > s.neutralBoll.UpBand.Last() { + if bidPrice.Float64() > s.neutralBoll.UpBand.Last(0) { canBuy = false log.Infof("tradeInBand is set, skip buy due to the price is higher than the neutralBB") } // Price too low in uptrend - if askPrice.Float64() < s.neutralBoll.DownBand.Last() { + if askPrice.Float64() < s.neutralBoll.DownBand.Last(0) { canSell = false log.Infof("tradeInBand is set, skip sell due to the price is lower than the neutralBB") } @@ -700,7 +700,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se closePrice = price } } - priceReverseEMA := fixedpoint.NewFromFloat(s.ReverseEMA.Last()) + priceReverseEMA := fixedpoint.NewFromFloat(s.ReverseEMA.Last(0)) // Main trend by ReverseEMA if closePrice.Compare(priceReverseEMA) > 0 { @@ -715,7 +715,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // closePrice is the close price of current kline closePrice := kline.GetClose() // priceReverseEMA is the current ReverseEMA price - priceReverseEMA := fixedpoint.NewFromFloat(s.ReverseEMA.Last()) + priceReverseEMA := fixedpoint.NewFromFloat(s.ReverseEMA.Last(0)) // Main trend by ReverseEMA s.mainTrendPrevious = s.mainTrendCurrent diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index 91f9f0eed9..55b6bed964 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -104,7 +104,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener // update pivot low data session.MarketDataStream.OnStart(func() { if s.updatePivotLow() { - bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivotLow.Last()) + bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivotLow.Last(0)) } s.pilotQuantityCalculation() @@ -117,7 +117,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener return } - bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivotLow.Last()) + bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivotLow.Last(0)) } })) @@ -260,7 +260,7 @@ func (s *BreakLow) pilotQuantityCalculation() { } func (s *BreakLow) updatePivotLow() bool { - low := fixedpoint.NewFromFloat(s.pivotLow.Last()) + low := fixedpoint.NewFromFloat(s.pivotLow.Last(0)) if low.IsZero() { return false } @@ -273,7 +273,7 @@ func (s *BreakLow) updatePivotLow() bool { s.pivotLowPrices = append(s.pivotLowPrices, low) } - fastLow := fixedpoint.NewFromFloat(s.fastPivotLow.Last()) + fastLow := fixedpoint.NewFromFloat(s.fastPivotLow.Last(0)) if !fastLow.IsZero() { if fastLow.Compare(s.lastLow) < 0 { s.lastLowInvalidated = true diff --git a/pkg/strategy/pivotshort/failedbreakhigh.go b/pkg/strategy/pivotshort/failedbreakhigh.go index 30ad0a32ef..f60cddfe8b 100644 --- a/pkg/strategy/pivotshort/failedbreakhigh.go +++ b/pkg/strategy/pivotshort/failedbreakhigh.go @@ -142,7 +142,7 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg // update pivot low data session.MarketDataStream.OnStart(func() { if s.updatePivotHigh() { - bbgo.Notify("%s new pivot high: %f", s.Symbol, s.pivotHigh.Last()) + bbgo.Notify("%s new pivot high: %f", s.Symbol, s.pivotHigh.Last(0)) } s.pilotQuantityCalculation() @@ -155,7 +155,7 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg return } - bbgo.Notify("%s new pivot high: %f", s.Symbol, s.pivotHigh.Last()) + bbgo.Notify("%s new pivot high: %f", s.Symbol, s.pivotHigh.Last(0)) } })) @@ -237,7 +237,7 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg } if s.vwma != nil { - vma := fixedpoint.NewFromFloat(s.vwma.Last()) + vma := fixedpoint.NewFromFloat(s.vwma.Last(0)) if kline.Volume.Compare(vma) < 0 { bbgo.Notify("%s %s kline volume %f is less than VMA %f, skip failed break high short", kline.Symbol, kline.Interval, kline.Volume.Float64(), vma.Float64()) return @@ -378,7 +378,7 @@ func (s *FailedBreakHigh) detectMacdDivergence() { } func (s *FailedBreakHigh) updatePivotHigh() bool { - high := fixedpoint.NewFromFloat(s.pivotHigh.Last()) + high := fixedpoint.NewFromFloat(s.pivotHigh.Last(0)) if high.IsZero() { return false } @@ -390,7 +390,7 @@ func (s *FailedBreakHigh) updatePivotHigh() bool { s.pivotHighPrices = append(s.pivotHighPrices, high) } - fastHigh := fixedpoint.NewFromFloat(s.fastPivotHigh.Last()) + fastHigh := fixedpoint.NewFromFloat(s.fastPivotHigh.Last(0)) if !fastHigh.IsZero() { if fastHigh.Compare(s.lastHigh) > 0 { // invalidate the last low diff --git a/pkg/strategy/pivotshort/resistance.go b/pkg/strategy/pivotshort/resistance.go index 744abefba7..357ff49abe 100644 --- a/pkg/strategy/pivotshort/resistance.go +++ b/pkg/strategy/pivotshort/resistance.go @@ -72,7 +72,7 @@ func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg s.resistancePivot = session.StandardIndicatorSet(s.Symbol).PivotLow(s.IntervalWindow) // use the last kline from the history before we get the next closed kline - s.updateResistanceOrders(fixedpoint.NewFromFloat(s.resistancePivot.Last())) + s.updateResistanceOrders(fixedpoint.NewFromFloat(s.resistancePivot.Last(0))) session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { // StrategyController diff --git a/pkg/strategy/pricedrop/strategy.go b/pkg/strategy/pricedrop/strategy.go index c8a9b16648..4d29330cd7 100644 --- a/pkg/strategy/pricedrop/strategy.go +++ b/pkg/strategy/pricedrop/strategy.go @@ -74,8 +74,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } - if kline.Close.Float64() > ema.Last() { - log.Warnf("kline close price %v is above EMA %s %f", kline.Close, ema.IntervalWindow, ema.Last()) + if kline.Close.Float64() > ema.Last(0) { + log.Warnf("kline close price %v is above EMA %s %f", kline.Close, ema.IntervalWindow, ema.Last(0)) return } diff --git a/pkg/strategy/rsmaker/strategy.go b/pkg/strategy/rsmaker/strategy.go index 2dd477a8fb..76f92438f1 100644 --- a/pkg/strategy/rsmaker/strategy.go +++ b/pkg/strategy/rsmaker/strategy.go @@ -329,9 +329,9 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k // baseBalance, hasBaseBalance := balances[s.Market.BaseCurrency] // quoteBalance, hasQuoteBalance := balances[s.Market.QuoteCurrency] - downBand := s.defaultBoll.DownBand.Last() - upBand := s.defaultBoll.UpBand.Last() - sma := s.defaultBoll.SMA.Last() + downBand := s.defaultBoll.DownBand.Last(0) + upBand := s.defaultBoll.UpBand.Last(0) + sma := s.defaultBoll.SMA.Last(0) log.Infof("bollinger band: up %f sma %f down %f", upBand, sma, downBand) bandPercentage := calculateBandPercentage(upBand, downBand, sma, midPrice.Float64()) diff --git a/pkg/strategy/schedule/strategy.go b/pkg/strategy/schedule/strategy.go index a647592373..5137ae01a7 100644 --- a/pkg/strategy/schedule/strategy.go +++ b/pkg/strategy/schedule/strategy.go @@ -127,7 +127,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se match := false // if any of the conditions satisfies then we execute order - if belowMA != nil && closePriceF < belowMA.Last() { + if belowMA != nil && closePriceF < belowMA.Last(0) { match = true if s.BelowMovingAverage != nil { if s.BelowMovingAverage.Side != nil { @@ -139,7 +139,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se quantity = s.BelowMovingAverage.QuantityOrAmount.CalculateQuantity(closePrice) } } - } else if aboveMA != nil && closePriceF > aboveMA.Last() { + } else if aboveMA != nil && closePriceF > aboveMA.Last(0) { match = true if s.AboveMovingAverage != nil { if s.AboveMovingAverage.Side != nil { diff --git a/pkg/strategy/skeleton/strategy.go b/pkg/strategy/skeleton/strategy.go index 536aaae10b..6651151fb1 100644 --- a/pkg/strategy/skeleton/strategy.go +++ b/pkg/strategy/skeleton/strategy.go @@ -98,7 +98,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // when a kline is closed, we will do something callback := func(kline types.KLine) { // get the latest ATR value from the indicator object that we just defined. - atrValue := atr.Last() + atrValue := atr.Last(0) log.Infof("atr %f", atrValue) // Update our counter and sync the changes to the persistence layer on time diff --git a/pkg/strategy/supertrend/double_dema.go b/pkg/strategy/supertrend/double_dema.go index e711f89f42..bbe1adde69 100644 --- a/pkg/strategy/supertrend/double_dema.go +++ b/pkg/strategy/supertrend/double_dema.go @@ -21,9 +21,9 @@ type DoubleDema struct { func (dd *DoubleDema) getDemaSignal(openPrice float64, closePrice float64) types.Direction { var demaSignal types.Direction = types.DirectionNone - if closePrice > dd.fastDEMA.Last() && closePrice > dd.slowDEMA.Last() && !(openPrice > dd.fastDEMA.Last() && openPrice > dd.slowDEMA.Last()) { + if closePrice > dd.fastDEMA.Last(0) && closePrice > dd.slowDEMA.Last(0) && !(openPrice > dd.fastDEMA.Last(0) && openPrice > dd.slowDEMA.Last(0)) { demaSignal = types.DirectionUp - } else if closePrice < dd.fastDEMA.Last() && closePrice < dd.slowDEMA.Last() && !(openPrice < dd.fastDEMA.Last() && openPrice < dd.slowDEMA.Last()) { + } else if closePrice < dd.fastDEMA.Last(0) && closePrice < dd.slowDEMA.Last(0) && !(openPrice < dd.fastDEMA.Last(0) && openPrice < dd.slowDEMA.Last(0)) { demaSignal = types.DirectionDown } diff --git a/pkg/strategy/supertrend/linreg.go b/pkg/strategy/supertrend/linreg.go index eb47c2c7b3..65813aa7f9 100644 --- a/pkg/strategy/supertrend/linreg.go +++ b/pkg/strategy/supertrend/linreg.go @@ -19,11 +19,11 @@ type LinReg struct { } // Last slope of linear regression baseline -func (lr *LinReg) Last() float64 { +func (lr *LinReg) Last(int) float64 { if lr.Values.Length() == 0 { return 0.0 } - return lr.Values.Last() + return lr.Values.Last(0) } // Index returns the slope of specified index @@ -68,7 +68,7 @@ func (lr *LinReg) Update(kline types.KLine) { startPrice := endPrice + slope*(length-1) lr.Values.Push((endPrice - startPrice) / (length - 1)) - log.Debugf("linear regression baseline slope: %f", lr.Last()) + log.Debugf("linear regression baseline slope: %f", lr.Last(0)) } func (lr *LinReg) BindK(target indicator.KLineClosedEmitter, symbol string, interval types.Interval) { @@ -96,9 +96,9 @@ func (lr *LinReg) GetSignal() types.Direction { var lrSignal types.Direction = types.DirectionNone switch { - case lr.Last() > 0: + case lr.Last(0) > 0: lrSignal = types.DirectionUp - case lr.Last() < 0: + case lr.Last(0) < 0: lrSignal = types.DirectionDown } diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 1003c621ee..ac39434563 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -110,7 +110,7 @@ func (r *AccumulatedProfitReport) DailyUpdate(tradeStats *types.TradeStats) { // Accumulated profit MA r.accumulatedProfitMA.Update(r.accumulatedProfit.Float64()) - r.accumulatedProfitMAPerDay.Update(r.accumulatedProfitMA.Last()) + r.accumulatedProfitMAPerDay.Update(r.accumulatedProfitMA.Last(0)) // Accumulated Fee r.accumulatedFeePerDay.Update(r.accumulatedFee.Float64()) @@ -606,14 +606,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.currentStopLossPrice = kline.GetLow() } if s.TakeProfitAtrMultiplier > 0 { - s.currentTakeProfitPrice = closePrice.Add(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitAtrMultiplier)) + s.currentTakeProfitPrice = closePrice.Add(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last(0) * s.TakeProfitAtrMultiplier)) } } else if side == types.SideTypeSell { if s.StopLossByTriggeringK { s.currentStopLossPrice = kline.GetHigh() } if s.TakeProfitAtrMultiplier > 0 { - s.currentTakeProfitPrice = closePrice.Sub(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitAtrMultiplier)) + s.currentTakeProfitPrice = closePrice.Sub(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last(0) * s.TakeProfitAtrMultiplier)) } } diff --git a/pkg/strategy/support/strategy.go b/pkg/strategy/support/strategy.go index d59788a3a0..b688d0cb4f 100644 --- a/pkg/strategy/support/strategy.go +++ b/pkg/strategy/support/strategy.go @@ -477,19 +477,19 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } } - if s.longTermEMA != nil && closePrice.Float64() < s.longTermEMA.Last() { + if s.longTermEMA != nil && closePrice.Float64() < s.longTermEMA.Last(0) { bbgo.Notify("%s: closed price is below the long term moving average line %f, skipping this support", s.Symbol, - s.longTermEMA.Last(), + s.longTermEMA.Last(0), kline, ) return } - if s.triggerEMA != nil && closePrice.Float64() > s.triggerEMA.Last() { + if s.triggerEMA != nil && closePrice.Float64() > s.triggerEMA.Last(0) { bbgo.Notify("%s: closed price is above the trigger moving average line %f, skipping this support", s.Symbol, - s.triggerEMA.Last(), + s.triggerEMA.Last(0), kline, ) return @@ -499,8 +499,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se bbgo.Notify("Found %s support: the close price %s is below trigger EMA %f and above long term EMA %f and volume %s > minimum volume %s", s.Symbol, closePrice.String(), - s.triggerEMA.Last(), - s.longTermEMA.Last(), + s.triggerEMA.Last(0), + s.longTermEMA.Last(0), kline.Volume.String(), s.MinVolume.String(), kline) diff --git a/pkg/strategy/swing/strategy.go b/pkg/strategy/swing/strategy.go index da85591ce2..fdb1234b4f 100644 --- a/pkg/strategy/swing/strategy.go +++ b/pkg/strategy/swing/strategy.go @@ -15,7 +15,7 @@ const ID = "swing" // Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data. type Float64Indicator interface { - Last() float64 + Last(int) float64 } func init() { @@ -104,7 +104,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } - movingAveragePrice := inc.Last() + movingAveragePrice := inc.Last(0) // skip it if it's near zero if movingAveragePrice < 0.0001 { diff --git a/pkg/strategy/techsignal/strategy.go b/pkg/strategy/techsignal/strategy.go index bf3326dd37..29a7c56be8 100644 --- a/pkg/strategy/techsignal/strategy.go +++ b/pkg/strategy/techsignal/strategy.go @@ -187,7 +187,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se }) } - var lastMA = ma.Last() + var lastMA = ma.Last(0) // skip if the closed price is above the moving average if closePrice.Float64() > lastMA { diff --git a/pkg/strategy/trendtrader/trend.go b/pkg/strategy/trendtrader/trend.go index be12f575a9..81876ef30a 100644 --- a/pkg/strategy/trendtrader/trend.go +++ b/pkg/strategy/trendtrader/trend.go @@ -65,15 +65,15 @@ func (s *TrendLine) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gene supportSlope2 := 0. session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { - if s.pivotHigh.Last() != resistancePrices.Last() { - resistancePrices.Update(s.pivotHigh.Last()) + if s.pivotHigh.Last(0) != resistancePrices.Last(0) { + resistancePrices.Update(s.pivotHigh.Last(0)) resistanceDuration.Update(pivotHighDurationCounter) pivotHighDurationCounter = 0 } else { pivotHighDurationCounter++ } - if s.pivotLow.Last() != supportPrices.Last() { - supportPrices.Update(s.pivotLow.Last()) + if s.pivotLow.Last(0) != supportPrices.Last(0) { + supportPrices.Update(s.pivotLow.Last(0)) supportDuration.Update(pivotLowDurationCounter) pivotLowDurationCounter = 0 } else { @@ -95,8 +95,8 @@ func (s *TrendLine) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gene if converge(resistanceSlope, supportSlope) { // y = mx+b - currentResistance := resistanceSlope*pivotHighDurationCounter + resistancePrices.Last() - currentSupport := supportSlope*pivotLowDurationCounter + supportPrices.Last() + currentResistance := resistanceSlope*pivotHighDurationCounter + resistancePrices.Last(0) + currentSupport := supportSlope*pivotLowDurationCounter + supportPrices.Last(0) log.Info(currentResistance, currentSupport, kline.Close) if kline.High.Float64() > currentResistance { diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 7ff87ce99e..a912cfeb17 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -302,8 +302,8 @@ func (s *Strategy) updateQuote(ctx context.Context, orderExecutionRouter bbgo.Or var pips = s.Pips if s.EnableBollBandMargin { - lastDownBand := fixedpoint.NewFromFloat(s.boll.DownBand.Last()) - lastUpBand := fixedpoint.NewFromFloat(s.boll.UpBand.Last()) + lastDownBand := fixedpoint.NewFromFloat(s.boll.DownBand.Last(0)) + lastUpBand := fixedpoint.NewFromFloat(s.boll.UpBand.Last(0)) if lastUpBand.IsZero() || lastDownBand.IsZero() { log.Warnf("bollinger band value is zero, skipping") diff --git a/pkg/types/filter.go b/pkg/types/filter.go index 58e0a966ec..bdb9e5de07 100644 --- a/pkg/types/filter.go +++ b/pkg/types/filter.go @@ -7,11 +7,7 @@ type FilterResult struct { c []int } -func (f *FilterResult) Last() float64 { - return f.Index(0) -} - -func (f *FilterResult) Index(j int) float64 { +func (f *FilterResult) Last(j int) float64 { if j >= f.length { return 0 } @@ -37,6 +33,10 @@ func (f *FilterResult) Index(j int) float64 { return 0 } +func (f *FilterResult) Index(j int) float64 { + return f.Last(j) +} + func (f *FilterResult) Length() int { return f.length } diff --git a/pkg/types/indicator.go b/pkg/types/indicator.go index 92c751a2ae..d8fe3870c6 100644 --- a/pkg/types/indicator.go +++ b/pkg/types/indicator.go @@ -12,186 +12,17 @@ import ( "github.com/c9s/bbgo/pkg/datatype/floats" ) -// 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 { - out := &Queue{ - arr: make([]float64, 0, size), - size: size, - } - out.SeriesBase.Series = out - return out -} - -func (inc *Queue) Last() float64 { - if len(inc.arr) == 0 { - return 0 - } - return inc.arr[len(inc.arr)-1] -} - -func (inc *Queue) Index(i int) float64 { - if len(inc.arr)-i-1 < 0 { - return 0 - } - return inc.arr[len(inc.arr)-i-1] -} - -func (inc *Queue) Length() int { - return len(inc.arr) -} - -func (inc *Queue) Clone() *Queue { - out := &Queue{ - arr: inc.arr[:], - size: inc.size, - } - out.SeriesBase.Series = out - return out -} - -func (inc *Queue) Update(v float64) { - inc.arr = append(inc.arr, v) - if len(inc.arr) > inc.size { - inc.arr = inc.arr[len(inc.arr)-inc.size:] - } -} - -var _ UpdatableSeriesExtend = &Queue{} - // Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data. type Float64Indicator interface { - Last() float64 -} - -// The interface maps to pinescript basic type `series` -// Access the internal historical data from the latest to the oldest -// Index(0) always maps to Last() -type Series interface { - Last() float64 - Index(int) float64 - Length() int -} - -type SeriesExtend interface { - Series - Sum(limit ...int) float64 - Mean(limit ...int) float64 - Abs() SeriesExtend - Predict(lookback int, offset ...int) float64 - NextCross(b Series, lookback int) (int, float64, bool) - CrossOver(b Series) BoolSeries - CrossUnder(b Series) BoolSeries - Highest(lookback int) float64 - Lowest(lookback int) float64 - Add(b interface{}) SeriesExtend - Minus(b interface{}) SeriesExtend - Div(b interface{}) SeriesExtend - Mul(b interface{}) SeriesExtend - Dot(b interface{}, limit ...int) float64 - Array(limit ...int) (result []float64) - Reverse(limit ...int) (result floats.Slice) - Change(offset ...int) SeriesExtend - 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 - AutoCorrelation(length int, lag ...int) float64 - Rank(length int) SeriesExtend - Sigmoid() SeriesExtend - Softmax(window int) SeriesExtend - Entropy(window int) float64 - CrossEntropy(b Series, window int) float64 - Filter(b func(i int, value float64) bool, length int) SeriesExtend -} - -func NewSeries(a Series) SeriesExtend { - return &SeriesBase{ - Series: a, - } -} - -type UpdatableSeries interface { - Series - Update(float64) -} - -type UpdatableSeriesExtend interface { - SeriesExtend - Update(float64) -} - -func Clone(u UpdatableSeriesExtend) UpdatableSeriesExtend { - method, ok := reflect.TypeOf(u).MethodByName("Clone") - if ok { - out := method.Func.Call([]reflect.Value{reflect.ValueOf(u)}) - return out[0].Interface().(UpdatableSeriesExtend) - } - panic("method Clone not exist") -} - -func TestUpdate(u UpdatableSeriesExtend, input float64) UpdatableSeriesExtend { - method, ok := reflect.TypeOf(u).MethodByName("TestUpdate") - if ok { - out := method.Func.Call([]reflect.Value{reflect.ValueOf(u), reflect.ValueOf(input)}) - return out[0].Interface().(UpdatableSeriesExtend) - } - panic("method TestUpdate not exist") -} - -// 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() -type BoolSeries interface { - Last() bool - Index(int) bool - Length() int -} - -// Calculate sum of the series -// if limit is given, will only sum first limit numbers (a.Index[0..limit]) -// otherwise will sum all elements -func Sum(a Series, limit ...int) (sum float64) { - l := a.Length() - if len(limit) > 0 && limit[0] < l { - l = limit[0] - } - for i := 0; i < l; i++ { - sum += a.Index(i) - } - return sum -} - -// Calculate the average value of the series -// if limit is given, will only calculate the average of first limit numbers (a.Index[0..limit]) -// otherwise will operate on all elements -func Mean(a Series, limit ...int) (mean float64) { - l := a.Length() - if l == 0 { - return 0 - } - if len(limit) > 0 && limit[0] < l { - l = limit[0] - } - return Sum(a, l) / float64(l) + Last(i int) float64 } type AbsResult struct { a Series } -func (a *AbsResult) Last() float64 { - return math.Abs(a.a.Last()) +func (a *AbsResult) Last(i int) float64 { + return math.Abs(a.a.Last(i)) } func (a *AbsResult) Index(i int) float64 { @@ -282,9 +113,9 @@ func (c *CrossResult) Last() bool { return false } if c.isOver { - return c.a.Last()-c.b.Last() > 0 && c.a.Index(1)-c.b.Index(1) < 0 + return c.a.Last(0)-c.b.Last(0) > 0 && c.a.Index(1)-c.b.Index(1) < 0 } else { - return c.a.Last()-c.b.Last() < 0 && c.a.Index(1)-c.b.Index(1) > 0 + return c.a.Last(0)-c.b.Last(0) < 0 && c.a.Index(1)-c.b.Index(1) > 0 } } @@ -328,7 +159,7 @@ func Highest(a Series, lookback int) float64 { if lookback > a.Length() { lookback = a.Length() } - highest := a.Last() + highest := a.Last(0) for i := 1; i < lookback; i++ { current := a.Index(i) if highest < current { @@ -342,7 +173,7 @@ func Lowest(a Series, lookback int) float64 { if lookback > a.Length() { lookback = a.Length() } - lowest := a.Last() + lowest := a.Last(0) for i := 1; i < lookback; i++ { current := a.Index(i) if lowest > current { @@ -354,7 +185,7 @@ func Lowest(a Series, lookback int) float64 { type NumberSeries float64 -func (a NumberSeries) Last() float64 { +func (a NumberSeries) Last(_ int) float64 { return float64(a) } @@ -384,12 +215,12 @@ func Add(a interface{}, b interface{}) SeriesExtend { return NewSeries(&AddSeriesResult{aa, bb}) } -func (a *AddSeriesResult) Last() float64 { - return a.a.Last() + a.b.Last() +func (a *AddSeriesResult) Last(i int) float64 { + return a.a.Last(i) + a.b.Last(i) } func (a *AddSeriesResult) Index(i int) float64 { - return a.a.Index(i) + a.b.Index(i) + return a.Last(i) } func (a *AddSeriesResult) Length() int { @@ -415,12 +246,12 @@ func Sub(a interface{}, b interface{}) SeriesExtend { return NewSeries(&MinusSeriesResult{aa, bb}) } -func (a *MinusSeriesResult) Last() float64 { - return a.a.Last() - a.b.Last() +func (a *MinusSeriesResult) Last(i int) float64 { + return a.a.Index(i) - a.b.Index(i) } func (a *MinusSeriesResult) Index(i int) float64 { - return a.a.Index(i) - a.b.Index(i) + return a.Last(i) } func (a *MinusSeriesResult) Length() int { @@ -471,12 +302,12 @@ type DivSeriesResult struct { b Series } -func (a *DivSeriesResult) Last() float64 { - return a.a.Last() / a.b.Last() +func (a *DivSeriesResult) Last(i int) float64 { + return a.a.Index(i) / a.b.Index(i) } func (a *DivSeriesResult) Index(i int) float64 { - return a.a.Index(i) / a.b.Index(i) + return a.Last(i) } func (a *DivSeriesResult) Length() int { @@ -502,12 +333,12 @@ type MulSeriesResult struct { b Series } -func (a *MulSeriesResult) Last() float64 { - return a.a.Last() * a.b.Last() +func (a *MulSeriesResult) Last(i int) float64 { + return a.a.Index(i) * a.b.Index(i) } func (a *MulSeriesResult) Index(i int) float64 { - return a.a.Index(i) * a.b.Index(i) + return a.Last(i) } func (a *MulSeriesResult) Length() int { @@ -654,18 +485,15 @@ type ChangeResult struct { offset int } -func (c *ChangeResult) Last() float64 { - if c.offset >= c.a.Length() { +func (c *ChangeResult) Last(i int) float64 { + if i+c.offset >= c.a.Length() { return 0 } - return c.a.Last() - c.a.Index(c.offset) + return c.a.Index(i) - c.a.Index(i+c.offset) } func (c *ChangeResult) Index(i int) float64 { - if i+c.offset >= c.a.Length() { - return 0 - } - return c.a.Index(i) - c.a.Index(i+c.offset) + return c.Last(i) } func (c *ChangeResult) Length() int { @@ -692,18 +520,15 @@ type PercentageChangeResult struct { offset int } -func (c *PercentageChangeResult) Last() float64 { - if c.offset >= c.a.Length() { +func (c *PercentageChangeResult) Last(i int) float64 { + if i+c.offset >= c.a.Length() { return 0 } - return c.a.Last()/c.a.Index(c.offset) - 1 + return c.a.Index(i)/c.a.Index(i+c.offset) - 1 } func (c *PercentageChangeResult) Index(i int) float64 { - if i+c.offset >= c.a.Length() { - return 0 - } - return c.a.Index(i)/c.a.Index(i+c.offset) - 1 + return c.Last(i) } func (c *PercentageChangeResult) Length() int { @@ -902,25 +727,21 @@ type ShiftResult struct { offset int } -func (inc *ShiftResult) Last() float64 { - if inc.offset < 0 { - return 0 - } - if inc.offset > inc.a.Length() { - return 0 - } - return inc.a.Index(inc.offset) -} -func (inc *ShiftResult) Index(i int) float64 { +func (inc *ShiftResult) Last(i int) float64 { if inc.offset+i < 0 { return 0 } if inc.offset+i > inc.a.Length() { return 0 } + return inc.a.Index(inc.offset + i) } +func (inc *ShiftResult) Index(i int) float64 { + return inc.Last(i) +} + func (inc *ShiftResult) Length() int { return inc.a.Length() - inc.offset } @@ -940,14 +761,16 @@ type SliceView struct { length int } -func (s *SliceView) Last() float64 { - return s.a.Index(s.start) -} -func (s *SliceView) Index(i int) float64 { +func (s *SliceView) Last(i int) float64 { if i >= s.length { return 0 } - return s.a.Index(i + s.start) + + return s.a.Last(i + s.start) +} + +func (s *SliceView) Index(i int) float64 { + return s.Last(i) } func (s *SliceView) Length() int { @@ -980,30 +803,6 @@ func Rolling(a Series, window int) *RollingResult { return &RollingResult{a, window} } -type SigmoidResult struct { - a Series -} - -func (s *SigmoidResult) Last() float64 { - return 1. / (1. + math.Exp(-s.a.Last())) -} - -func (s *SigmoidResult) Index(i int) float64 { - return 1. / (1. + math.Exp(-s.a.Index(i))) -} - -func (s *SigmoidResult) Length() int { - return s.a.Length() -} - -// Sigmoid returns the input values in range of -1 to 1 -// along the sigmoid or s-shaped curve. -// Commonly used in machine learning while training neural networks -// as an activation function. -func Sigmoid(a Series) SeriesExtend { - return NewSeries(&SigmoidResult{a}) -} - // SoftMax returns the input value in the range of 0 to 1 // with sum of all the probabilities being equal to one. // It is commonly used in machine learning neural networks. diff --git a/pkg/types/indicator_test.go b/pkg/types/indicator_test.go index 458d9dcf25..19c265ea81 100644 --- a/pkg/types/indicator_test.go +++ b/pkg/types/indicator_test.go @@ -15,7 +15,7 @@ import ( func TestQueue(t *testing.T) { zeroq := NewQueue(0) - assert.Equal(t, zeroq.Last(), 0.) + assert.Equal(t, zeroq.Last(0), 0.) assert.Equal(t, zeroq.Index(0), 0.) zeroq.Update(1.) assert.Equal(t, zeroq.Length(), 0) @@ -23,7 +23,7 @@ func TestQueue(t *testing.T) { func TestFloat(t *testing.T) { var a Series = Sub(3., 2.) - assert.Equal(t, a.Last(), 1.) + assert.Equal(t, a.Last(0), 1.) assert.Equal(t, a.Index(100), 1.) } @@ -47,7 +47,7 @@ func TestFloat64Slice(t *testing.T) { var c Series = Sub(&a, &b) a = append(a, 4.0) b = append(b, 3.0) - assert.Equal(t, c.Last(), 1.) + assert.Equal(t, c.Last(0), 1.) } /* @@ -128,7 +128,7 @@ func TestSigmoid(t *testing.T) { out := Sigmoid(&a) r := floats.Slice{0.9525741268224334, 0.7310585786300049, 0.8909031788043871} for i := 0; i < out.Length(); i++ { - assert.InDelta(t, r.Index(i), out.Index(i), 0.001) + assert.InDelta(t, r.Index(i), out.Index(i), 0.001, "i=%d", i) } } @@ -142,7 +142,7 @@ func TestAdd(t *testing.T) { var a NumberSeries = 3.0 var b NumberSeries = 2.0 out := Add(&a, &b) - assert.Equal(t, out.Last(), 5.0) + assert.Equal(t, out.Last(0), 5.0) assert.Equal(t, out.Index(0), 5.0) assert.Equal(t, out.Length(), math.MaxInt32) } @@ -151,16 +151,16 @@ func TestDiv(t *testing.T) { a := floats.Slice{3.0, 1.0, 2.0} b := NumberSeries(2.0) out := Div(&a, &b) - assert.Equal(t, out.Last(), 1.0) - assert.Equal(t, out.Length(), 3) - assert.Equal(t, out.Index(1), 0.5) + assert.Equal(t, 1.0, out.Last(0)) + assert.Equal(t, 3, out.Length()) + assert.Equal(t, 0.5, out.Index(1)) } func TestMul(t *testing.T) { a := floats.Slice{3.0, 1.0, 2.0} b := NumberSeries(2.0) out := Mul(&a, &b) - assert.Equal(t, out.Last(), 4.0) + assert.Equal(t, out.Last(0), 4.0) assert.Equal(t, out.Length(), 3) assert.Equal(t, out.Index(1), 2.0) } @@ -183,11 +183,11 @@ func TestSwitchInterface(t *testing.T) { var d float32 = 4.0 var df float64 = 4.0 var e float64 = 5.0 - assert.Equal(t, switchIface(a).Last(), af) - assert.Equal(t, switchIface(b).Last(), bf) - assert.Equal(t, switchIface(c).Last(), cf) - assert.Equal(t, switchIface(d).Last(), df) - assert.Equal(t, switchIface(e).Last(), e) + assert.Equal(t, switchIface(a).Last(0), af) + assert.Equal(t, switchIface(b).Last(0), bf) + assert.Equal(t, switchIface(c).Last(0), cf) + assert.Equal(t, switchIface(d).Last(0), df) + assert.Equal(t, switchIface(e).Last(0), e) } // from https://en.wikipedia.org/wiki/Logistic_regression @@ -223,8 +223,8 @@ func TestClone(t *testing.T) { a.Update(3.) b := Clone(a) b.Update(4.) - assert.Equal(t, a.Last(), 3.) - assert.Equal(t, b.Last(), 4.) + assert.Equal(t, a.Last(0), 3.) + assert.Equal(t, b.Last(0), 4.) } func TestPlot(t *testing.T) { @@ -244,6 +244,6 @@ func TestFilter(t *testing.T) { return val > 0 }, 4) assert.Equal(t, b.Length(), 4) - assert.Equal(t, b.Last(), 1000.) + assert.Equal(t, b.Last(0), 1000.) assert.Equal(t, b.Sum(3), 1200.) } diff --git a/pkg/types/kline.go b/pkg/types/kline.go index 9f8dd5d724..fcc358176b 100644 --- a/pkg/types/kline.go +++ b/pkg/types/kline.go @@ -597,7 +597,7 @@ type KLineSeries struct { kv KValueType } -func (k *KLineSeries) Last() float64 { +func (k *KLineSeries) Last(int) float64 { length := len(*k.lines) switch k.kv { case kOpUnknown: diff --git a/pkg/types/queue.go b/pkg/types/queue.go new file mode 100644 index 0000000000..5205f802f8 --- /dev/null +++ b/pkg/types/queue.go @@ -0,0 +1,51 @@ +package types + +// 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 { + out := &Queue{ + arr: make([]float64, 0, size), + size: size, + } + out.SeriesBase.Series = out + return out +} + +func (inc *Queue) Last(i int) float64 { + if i < 0 || len(inc.arr)-i-1 < 0 { + return 0 + } + return inc.arr[len(inc.arr)-1-i] +} + +func (inc *Queue) Index(i int) float64 { + return inc.Last(i) +} + +func (inc *Queue) Length() int { + return len(inc.arr) +} + +func (inc *Queue) Clone() *Queue { + out := &Queue{ + arr: inc.arr[:], + size: inc.size, + } + out.SeriesBase.Series = out + return out +} + +func (inc *Queue) Update(v float64) { + inc.arr = append(inc.arr, v) + if len(inc.arr) > inc.size { + inc.arr = inc.arr[len(inc.arr)-inc.size:] + } +} + +var _ UpdatableSeriesExtend = &Queue{} diff --git a/pkg/types/series.go b/pkg/types/series.go new file mode 100644 index 0000000000..1c4289ee28 --- /dev/null +++ b/pkg/types/series.go @@ -0,0 +1,123 @@ +package types + +import ( + "reflect" + + "github.com/c9s/bbgo/pkg/datatype/floats" +) + +// The interface maps to pinescript basic type `series` +// Access the internal historical data from the latest to the oldest +// Index(0) always maps to Last() +type Series interface { + Last(i int) float64 + Index(i int) float64 + Length() int +} + +type SeriesExtend interface { + Series + Sum(limit ...int) float64 + Mean(limit ...int) float64 + Abs() SeriesExtend + Predict(lookback int, offset ...int) float64 + NextCross(b Series, lookback int) (int, float64, bool) + CrossOver(b Series) BoolSeries + CrossUnder(b Series) BoolSeries + Highest(lookback int) float64 + Lowest(lookback int) float64 + Add(b interface{}) SeriesExtend + Minus(b interface{}) SeriesExtend + Div(b interface{}) SeriesExtend + Mul(b interface{}) SeriesExtend + Dot(b interface{}, limit ...int) float64 + Array(limit ...int) (result []float64) + Reverse(limit ...int) (result floats.Slice) + Change(offset ...int) SeriesExtend + 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 + AutoCorrelation(length int, lag ...int) float64 + Rank(length int) SeriesExtend + Sigmoid() SeriesExtend + Softmax(window int) SeriesExtend + Entropy(window int) float64 + CrossEntropy(b Series, window int) float64 + Filter(b func(i int, value float64) bool, length int) SeriesExtend +} + +func NewSeries(a Series) SeriesExtend { + return &SeriesBase{ + Series: a, + } +} + +type UpdatableSeries interface { + Series + Update(float64) +} + +type UpdatableSeriesExtend interface { + SeriesExtend + Update(float64) +} + +func Clone(u UpdatableSeriesExtend) UpdatableSeriesExtend { + method, ok := reflect.TypeOf(u).MethodByName("Clone") + if ok { + out := method.Func.Call([]reflect.Value{reflect.ValueOf(u)}) + return out[0].Interface().(UpdatableSeriesExtend) + } + panic("method Clone not exist") +} + +func TestUpdate(u UpdatableSeriesExtend, input float64) UpdatableSeriesExtend { + method, ok := reflect.TypeOf(u).MethodByName("TestUpdate") + if ok { + out := method.Func.Call([]reflect.Value{reflect.ValueOf(u), reflect.ValueOf(input)}) + return out[0].Interface().(UpdatableSeriesExtend) + } + panic("method TestUpdate not exist") +} + +// 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() +type BoolSeries interface { + Last() bool + Index(int) bool + Length() int +} + +// Calculate sum of the series +// if limit is given, will only sum first limit numbers (a.Index[0..limit]) +// otherwise will sum all elements +func Sum(a Series, limit ...int) (sum float64) { + l := a.Length() + if len(limit) > 0 && limit[0] < l { + l = limit[0] + } + for i := 0; i < l; i++ { + sum += a.Index(i) + } + return sum +} + +// Calculate the average value of the series +// if limit is given, will only calculate the average of first limit numbers (a.Index[0..limit]) +// otherwise will operate on all elements +func Mean(a Series, limit ...int) (mean float64) { + l := a.Length() + if l == 0 { + return 0 + } + if len(limit) > 0 && limit[0] < l { + l = limit[0] + } + return Sum(a, l) / float64(l) +} diff --git a/pkg/types/seriesbase_imp.go b/pkg/types/seriesbase_imp.go index feb95645af..1e52ce5449 100644 --- a/pkg/types/seriesbase_imp.go +++ b/pkg/types/seriesbase_imp.go @@ -1,6 +1,8 @@ package types -import "github.com/c9s/bbgo/pkg/datatype/floats" +import ( + "github.com/c9s/bbgo/pkg/datatype/floats" +) // SeriesBase is a wrapper of the Series interface // You can assign a data container that implements the Series interface @@ -13,14 +15,14 @@ func (s *SeriesBase) Index(i int) float64 { if s.Series == nil { return 0 } - return s.Series.Index(i) + return s.Series.Last(i) } -func (s *SeriesBase) Last() float64 { +func (s *SeriesBase) Last(int) float64 { if s.Series == nil { return 0 } - return s.Series.Last() + return s.Series.Last(0) } func (s *SeriesBase) Length() int { diff --git a/pkg/types/sigmoid.go b/pkg/types/sigmoid.go new file mode 100644 index 0000000000..4b28bb23b9 --- /dev/null +++ b/pkg/types/sigmoid.go @@ -0,0 +1,27 @@ +package types + +import "math" + +type SigmoidResult struct { + a Series +} + +func (s *SigmoidResult) Last(i int) float64 { + return 1. / (1. + math.Exp(-s.a.Last(i))) +} + +func (s *SigmoidResult) Index(i int) float64 { + return s.Last(i) +} + +func (s *SigmoidResult) Length() int { + return s.a.Length() +} + +// Sigmoid returns the input values in range of -1 to 1 +// along the sigmoid or s-shaped curve. +// Commonly used in machine learning while training neural networks +// as an activation function. +func Sigmoid(a Series) SeriesExtend { + return NewSeries(&SigmoidResult{a}) +}