all: add parameter index to the Last method

This commit is contained in:
c9s 2023-05-31 19:35:44 +08:00
parent 2a074ba11b
commit 5515f588e3
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
142 changed files with 845 additions and 998 deletions

View File

@ -48,7 +48,7 @@ func (s *LowerShadowTakeProfit) Bind(session *ExchangeSession, orderExecutor *Ge
} }
// skip close price higher than the ewma // skip close price higher than the ewma
if closePrice.Float64() > ewma.Last() { if closePrice.Float64() > ewma.Last(0) {
return return
} }

View File

@ -21,7 +21,7 @@ func (s *StopEMA) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExec
} }
func (s *StopEMA) Allowed(closePrice fixedpoint.Value) bool { func (s *StopEMA) Allowed(closePrice fixedpoint.Value) bool {
ema := fixedpoint.NewFromFloat(s.stopEWMA.Last()) ema := fixedpoint.NewFromFloat(s.stopEWMA.Last(0))
if ema.IsZero() { if ema.IsZero() {
logrus.Infof("stopEMA protection: value is zero, skip") logrus.Infof("stopEMA protection: value is zero, skip")
return false return false

View File

@ -29,12 +29,12 @@ func (s *TrendEMA) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExe
} }
s.last = s.ewma.Values[s.ewma.Length()-2] 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) { session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, s.Interval, func(kline types.KLine) {
s.last = s.current s.last = s.current
s.current = s.ewma.Last() s.current = s.ewma.Last(0)
})) }))
} }

View File

@ -183,12 +183,12 @@ func (s Slice) Addr() *Slice {
} }
// Last, Index, Length implements the types.Series interface // Last, Index, Length implements the types.Series interface
func (s Slice) Last() float64 { func (s Slice) Last(i int) float64 {
length := len(s) length := len(s)
if length > 0 { if i < 0 || length-1-i < 0 {
return s[length-1] return 0.0
} }
return 0.0 return s[length-1-i]
} }
// Index fetches the element from the end of the slice // Index fetches the element from the end of the slice

View File

@ -35,15 +35,16 @@ func (inc *AD) Update(high, low, cloze, volume float64) {
moneyFlowVolume = ((2*cloze - high - low) / (high - low)) * volume moneyFlowVolume = ((2*cloze - high - low) / (high - low)) * volume
} }
ad := inc.Last() + moneyFlowVolume ad := inc.Last(0) + moneyFlowVolume
inc.Values.Push(ad) inc.Values.Push(ad)
} }
func (inc *AD) Last() float64 { func (inc *AD) Last(i int) float64 {
if len(inc.Values) == 0 { length := len(inc.Values)
return 0.0 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 { 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.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() inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
} }

View File

@ -20,6 +20,7 @@ import (
// //
// @param offset: Gaussian applied to the combo line. 1->ema, 0->sma // @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 // @param sigma: the standard deviation applied to the combo line. This makes the combo line sharper
//
//go:generate callbackgen -type ALMA //go:generate callbackgen -type ALMA
type ALMA struct { type ALMA struct {
types.SeriesBase types.SeriesBase
@ -64,11 +65,11 @@ func (inc *ALMA) Update(value float64) {
} }
} }
func (inc *ALMA) Last() float64 { func (inc *ALMA) Last(i int) float64 {
if len(inc.Values) == 0 { if i >= len(inc.Values) {
return 0 return 0
} }
return inc.Values[len(inc.Values)-1] return inc.Values[len(inc.Values)-i-1]
} }
func (inc *ALMA) Index(i int) float64 { func (inc *ALMA) Index(i int) float64 {
@ -88,12 +89,12 @@ func (inc *ALMA) CalculateAndUpdate(allKLines []types.KLine) {
if inc.input == nil { if inc.input == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.Update(k.Close.Float64()) inc.Update(k.Close.Float64())
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
return return
} }
inc.Update(allKLines[len(allKLines)-1].Close.Float64()) 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) { func (inc *ALMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {

View File

@ -53,7 +53,7 @@ func Test_ALMA(t *testing.T) {
Sigma: 6, Sigma: 6,
} }
alma.CalculateAndUpdate(tt.kLines) 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.InDelta(t, tt.next, alma.Index(1), Delta)
assert.Equal(t, tt.all, alma.Length()) assert.Equal(t, tt.all, alma.Length())
}) })

View File

@ -74,18 +74,18 @@ func (inc *ATR) Update(high, low, cloze float64) {
// apply rolling moving average // apply rolling moving average
inc.RMA.Update(trueRange) inc.RMA.Update(trueRange)
atr := inc.RMA.Last() atr := inc.RMA.Last(0)
inc.PercentageVolatility.Push(atr / cloze) inc.PercentageVolatility.Push(atr / cloze)
if len(inc.PercentageVolatility) > MaxNumOfATR { if len(inc.PercentageVolatility) > MaxNumOfATR {
inc.PercentageVolatility = inc.PercentageVolatility[MaxNumOfATRTruncateSize-1:] inc.PercentageVolatility = inc.PercentageVolatility[MaxNumOfATRTruncateSize-1:]
} }
} }
func (inc *ATR) Last() float64 { func (inc *ATR) Last(i int) float64 {
if inc.RMA == nil { if inc.RMA == nil {
return 0 return 0
} }
return inc.RMA.Last() return inc.RMA.Last(i)
} }
func (inc *ATR) Index(i int) float64 { 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.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }

View File

@ -65,7 +65,7 @@ func Test_calculateATR(t *testing.T) {
atr.PushK(k) atr.PushK(k)
} }
got := atr.Last() got := atr.Last(0)
diff := math.Trunc((got-tt.want)*100) / 100 diff := math.Trunc((got-tt.want)*100) / 100
if diff != 0 { if diff != 0 {
t.Errorf("calculateATR() = %v, want %v", got, tt.want) t.Errorf("calculateATR() = %v, want %v", got, tt.want)

View File

@ -21,7 +21,7 @@ import (
// //
// Calculation: // Calculation:
// //
// ATRP = (Average True Range / Close) * 100 // ATRP = (Average True Range / Close) * 100
// //
//go:generate callbackgen -type ATRP //go:generate callbackgen -type ATRP
type ATRP struct { type ATRP struct {
@ -69,15 +69,15 @@ func (inc *ATRP) Update(high, low, cloze float64) {
// apply rolling moving average // apply rolling moving average
inc.RMA.Update(trueRange) inc.RMA.Update(trueRange)
atr := inc.RMA.Last() atr := inc.RMA.Last(0)
inc.PercentageVolatility.Push(atr / cloze) inc.PercentageVolatility.Push(atr / cloze)
} }
func (inc *ATRP) Last() float64 { func (inc *ATRP) Last(i int) float64 {
if inc.RMA == nil { if inc.RMA == nil {
return 0 return 0
} }
return inc.RMA.Last() return inc.RMA.Last(i)
} }
func (inc *ATRP) Index(i int) float64 { func (inc *ATRP) Index(i int) float64 {
@ -109,7 +109,7 @@ func (inc *ATRP) CalculateAndUpdate(kLines []types.KLine) {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
inc.EndTime = kLines[len(kLines)-1].EndTime.Time() inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
} }

View File

@ -84,8 +84,8 @@ func (inc *BOLL) Update(value float64) {
inc.SMA.Update(value) inc.SMA.Update(value)
inc.StdDev.Update(value) inc.StdDev.Update(value)
var sma = inc.SMA.Last() var sma = inc.SMA.Last(0)
var stdDev = inc.StdDev.Last() var stdDev = inc.StdDev.Last(0)
var band = inc.K * stdDev var band = inc.K * stdDev
var upBand = sma + band var upBand = sma + band
@ -105,7 +105,7 @@ func (inc *BOLL) PushK(k types.KLine) {
} }
inc.Update(k.Close.Float64()) inc.Update(k.Close.Float64())
inc.EndTime = k.EndTime.Time() 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) { func (inc *BOLL) LoadK(allKLines []types.KLine) {
@ -113,7 +113,7 @@ func (inc *BOLL) LoadK(allKLines []types.KLine) {
inc.PushK(k) 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) { func (inc *BOLL) CalculateAndUpdate(allKLines []types.KLine) {

View File

@ -61,8 +61,8 @@ func TestBOLL(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
boll := BOLL{IntervalWindow: types.IntervalWindow{Window: tt.window}, K: tt.k} boll := BOLL{IntervalWindow: types.IntervalWindow{Window: tt.window}, K: tt.k}
boll.CalculateAndUpdate(tt.kLines) boll.CalculateAndUpdate(tt.kLines)
assert.InDelta(t, tt.up, boll.UpBand.Last(), Delta) assert.InDelta(t, tt.up, boll.UpBand.Last(0), Delta)
assert.InDelta(t, tt.down, boll.DownBand.Last(), Delta) assert.InDelta(t, tt.down, boll.DownBand.Last(0), Delta)
}) })
} }

View File

@ -43,7 +43,7 @@ func (inc *CCI) Update(value float64) {
} }
inc.Input.Push(value) 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) inc.TypicalPrice.Push(tp)
if len(inc.Input) < inc.Window { if len(inc.Input) < inc.Window {
return return
@ -55,7 +55,7 @@ func (inc *CCI) Update(value float64) {
} }
md := 0. md := 0.
for i := 0; i < inc.Window; i++ { for i := 0; i < inc.Window; i++ {
diff := inc.Input.Index(i) - ma diff := inc.Input.Last(i) - ma
md += diff * diff md += diff * diff
} }
md = math.Sqrt(md / float64(inc.Window)) md = math.Sqrt(md / float64(inc.Window))
@ -68,18 +68,12 @@ func (inc *CCI) Update(value float64) {
} }
} }
func (inc *CCI) Last() float64 { func (inc *CCI) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *CCI) Index(i int) float64 { func (inc *CCI) Index(i int) float64 {
if i >= len(inc.Values) { return inc.Last(i)
return 0
}
return inc.Values[len(inc.Values)-1-i]
} }
func (inc *CCI) Length() int { func (inc *CCI) Length() int {
@ -96,12 +90,12 @@ func (inc *CCI) CalculateAndUpdate(allKLines []types.KLine) {
if inc.TypicalPrice.Length() == 0 { if inc.TypicalPrice.Length() == 0 {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -29,7 +29,7 @@ func Test_CCI(t *testing.T) {
cci.Update(value) cci.Update(value)
} }
last := cci.Last() last := cci.Last(0)
assert.InDelta(t, 93.250481, last, Delta) assert.InDelta(t, 93.250481, last, Delta)
assert.InDelta(t, 81.813449, cci.Index(1), Delta) assert.InDelta(t, 81.813449, cci.Index(1), Delta)
assert.Equal(t, 50-16+1, cci.Length()) assert.Equal(t, 50-16+1, cci.Length())

View File

@ -7,12 +7,13 @@ import (
// Refer: Cumulative Moving Average, Cumulative Average // Refer: Cumulative Moving Average, Cumulative Average
// Refer: https://en.wikipedia.org/wiki/Moving_average // Refer: https://en.wikipedia.org/wiki/Moving_average
//
//go:generate callbackgen -type CA //go:generate callbackgen -type CA
type CA struct { type CA struct {
types.SeriesBase types.SeriesBase
Interval types.Interval Interval types.Interval
Values floats.Slice Values floats.Slice
length float64 length float64
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
@ -20,7 +21,7 @@ func (inc *CA) Update(x float64) {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc 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.length += 1
inc.Values.Push(newVal) inc.Values.Push(newVal)
if len(inc.Values) > MaxNumOfEWMA { if len(inc.Values) > MaxNumOfEWMA {
@ -29,18 +30,12 @@ func (inc *CA) Update(x float64) {
} }
} }
func (inc *CA) Last() float64 { func (inc *CA) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *CA) Index(i int) float64 { func (inc *CA) Index(i int) float64 {
if i >= len(inc.Values) { return inc.Last(i)
return 0
}
return inc.Values[len(inc.Values)-1-i]
} }
func (inc *CA) Length() int { func (inc *CA) Length() int {
@ -56,7 +51,7 @@ func (inc *CA) PushK(k types.KLine) {
func (inc *CA) CalculateAndUpdate(allKLines []types.KLine) { func (inc *CA) CalculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -51,22 +51,19 @@ func (inc *DEMA) Update(value float64) {
} }
inc.a1.Update(value) inc.a1.Update(value)
inc.a2.Update(inc.a1.Last()) inc.a2.Update(inc.a1.Last(0))
inc.Values.Push(2*inc.a1.Last() - inc.a2.Last()) inc.Values.Push(2*inc.a1.Last(0) - inc.a2.Last(0))
if len(inc.Values) > MaxNumOfEWMA { if len(inc.Values) > MaxNumOfEWMA {
inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:]
} }
} }
func (inc *DEMA) Last() float64 { func (inc *DEMA) Last(i int) float64 {
return inc.Values.Last() return inc.Values.Last(i)
} }
func (inc *DEMA) Index(i int) float64 { func (inc *DEMA) Index(i int) float64 {
if len(inc.Values)-i-1 >= 0 { return inc.Last(i)
return inc.Values[len(inc.Values)-1-i]
}
return 0
} }
func (inc *DEMA) Length() int { func (inc *DEMA) Length() int {
@ -83,13 +80,13 @@ func (inc *DEMA) CalculateAndUpdate(allKLines []types.KLine) {
if inc.a1 == nil { if inc.a1 == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} else { } else {
// last k // last k
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -46,7 +46,7 @@ func Test_DEMA(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
dema := DEMA{IntervalWindow: types.IntervalWindow{Window: 16}} dema := DEMA{IntervalWindow: types.IntervalWindow{Window: 16}}
dema.CalculateAndUpdate(tt.kLines) dema.CalculateAndUpdate(tt.kLines)
last := dema.Last() last := dema.Last(0)
assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.want, last, Delta)
assert.InDelta(t, tt.next, dema.Index(1), Delta) assert.InDelta(t, tt.next, dema.Index(1), Delta)
assert.Equal(t, tt.all, dema.Length()) assert.Equal(t, tt.all, dema.Length())

View File

@ -72,9 +72,9 @@ func (inc *DMI) Update(high, low, cloze float64) {
if inc.atr.Length() < inc.Window { if inc.atr.Length() < inc.Window {
return return
} }
k := 100. / inc.atr.Last() k := 100. / inc.atr.Last(0)
dmp := inc.DMP.Last() dmp := inc.DMP.Last(0)
dmn := inc.DMN.Last() dmn := inc.DMN.Last(0)
inc.DIPlus.Update(k * dmp) inc.DIPlus.Update(k * dmp)
inc.DIMinus.Update(k * dmn) inc.DIMinus.Update(k * dmn)
dx := 100. * math.Abs(dmp-dmn) / (dmp + dmn) dx := 100. * math.Abs(dmp-dmn) / (dmp + dmn)
@ -108,11 +108,11 @@ func (inc *DMI) CalculateAndUpdate(allKLines []types.KLine) {
if inc.ADX == nil { if inc.ADX == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) 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 { } else {
inc.PushK(last) 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))
} }
} }

View File

@ -78,9 +78,9 @@ func Test_DMI(t *testing.T) {
ADXSmoothing: 14, ADXSmoothing: 14,
} }
dmi.CalculateAndUpdate(tt.klines) dmi.CalculateAndUpdate(tt.klines)
assert.InDelta(t, dmi.GetDIPlus().Last(), tt.want.dip, Delta) assert.InDelta(t, dmi.GetDIPlus().Last(0), tt.want.dip, Delta)
assert.InDelta(t, dmi.GetDIMinus().Last(), tt.want.dim, Delta) assert.InDelta(t, dmi.GetDIMinus().Last(0), tt.want.dim, Delta)
assert.InDelta(t, dmi.GetADX().Last(), tt.want.adx, Delta) assert.InDelta(t, dmi.GetADX().Last(0), tt.want.adx, Delta)
}) })
} }

View File

@ -51,7 +51,7 @@ func (inc *Drift) Update(value float64) {
inc.chng.Update(chng) inc.chng.Update(chng)
if inc.chng.Length() >= inc.Window { if inc.chng.Length() >= inc.Window {
stdev := types.Stdev(inc.chng, 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) inc.Values.Push(drift)
} }
} }
@ -74,7 +74,7 @@ func (inc *Drift) ZeroPoint() float64 {
} else { } else {
return N2 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) { func (inc *Drift) Clone() (out *Drift) {
@ -96,17 +96,11 @@ func (inc *Drift) TestUpdate(value float64) *Drift {
} }
func (inc *Drift) Index(i int) float64 { func (inc *Drift) Index(i int) float64 {
if inc.Values == nil { return inc.Last(i)
return 0
}
return inc.Values.Index(i)
} }
func (inc *Drift) Last() float64 { func (inc *Drift) Last(i int) float64 {
if inc.Values.Length() == 0 { return inc.Values.Last(i)
return 0
}
return inc.Values.Last()
} }
func (inc *Drift) Length() int { func (inc *Drift) Length() int {
@ -126,12 +120,12 @@ func (inc *Drift) CalculateAndUpdate(allKLines []types.KLine) {
if inc.chng == nil { if inc.chng == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -55,11 +55,12 @@ func (inc *EMV) Index(i int) float64 {
return inc.Values.Index(i) return inc.Values.Index(i)
} }
func (inc *EMV) Last() float64 { func (inc *EMV) Last(i int) float64 {
if inc.Values == nil { if inc.Values == nil {
return 0 return 0
} }
return inc.Values.Last()
return inc.Values.Last(i)
} }
func (inc *EMV) Length() int { func (inc *EMV) Length() int {

View File

@ -16,7 +16,7 @@ func Test_EMV(t *testing.T) {
} }
emv.Update(63.74, 62.63, 32178836) emv.Update(63.74, 62.63, 32178836)
emv.Update(64.51, 63.85, 36461672) 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.57, 63.81, 51372680)
emv.Update(64.31, 62.62, 42476356) emv.Update(64.31, 62.62, 42476356)
emv.Update(63.43, 62.73, 29504176) 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(65.25, 64.48, 37015388)
emv.Update(64.69, 63.65, 40672116) emv.Update(64.69, 63.65, 40672116)
emv.Update(64.26, 63.68, 35627200) emv.Update(64.26, 63.68, 35627200)
assert.InDelta(t, -0.03, emv.Last(), Delta) assert.InDelta(t, -0.03, emv.Last(0), Delta)
} }

View File

@ -50,24 +50,20 @@ func (inc *EWMA) Update(value float64) {
inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] 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) inc.Values.Push(ema)
} }
func (inc *EWMA) Last() float64 { func (inc *EWMA) Last(i int) float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0 return 0
} }
return inc.Values[len(inc.Values)-1] return inc.Values.Last(i)
} }
func (inc *EWMA) Index(i int) float64 { func (inc *EWMA) Index(i int) float64 {
if i >= len(inc.Values) { return inc.Last(i)
return 0
}
return inc.Values[len(inc.Values)-1-i]
} }
func (inc *EWMA) Length() int { func (inc *EWMA) Length() int {
@ -81,7 +77,7 @@ func (inc *EWMA) PushK(k types.KLine) {
inc.Update(k.Close.Float64()) inc.Update(k.Close.Float64())
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
func CalculateKLinesEMA(allKLines []types.KLine, priceF KLineValueMapper, window int) float64 { func CalculateKLinesEMA(allKLines []types.KLine, priceF KLineValueMapper, window int) float64 {

View File

@ -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, // 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 // 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. // along with the security's price.
//
//go:generate callbackgen -type FisherTransform //go:generate callbackgen -type FisherTransform
type FisherTransform struct { type FisherTransform struct {
types.SeriesBase 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 { if inc.Values == nil {
return 0.0 return 0.0
} }
return inc.Values.Last() return inc.Values.Last(i)
} }
func (inc *FisherTransform) Index(i int) float64 { func (inc *FisherTransform) Index(i int) float64 {
if inc.Values == nil { return inc.Last(i)
return 0.0
}
return inc.Values.Index(i)
} }
func (inc *FisherTransform) Length() int { func (inc *FisherTransform) Length() int {

View File

@ -23,16 +23,12 @@ func NewFloat64Series(v ...float64) Float64Series {
return s return s
} }
func (f *Float64Series) Last() float64 { func (f *Float64Series) Last(i int) float64 {
return f.slice.Last() return f.slice.Last(i)
} }
func (f *Float64Series) Index(i int) float64 { func (f *Float64Series) Index(i int) float64 {
length := len(f.slice) return f.slice.Last(i)
if length == 0 || length-i-1 < 0 {
return 0
}
return f.slice[length-i-1]
} }
func (f *Float64Series) Length() int { func (f *Float64Series) Length() int {

View File

@ -1,9 +1,10 @@
package indicator package indicator
import ( import (
"math"
"github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/datatype/floats"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
"math"
) )
// Refer: https://jamesgoulding.com/Research_II/Ehlers/Ehlers%20(Optimal%20Tracking%20Filters).doc // 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 lambda := inc.a / inc.b
lambda2 := lambda * lambda lambda2 := lambda * lambda
alpha := (-lambda2 + math.Sqrt(lambda2*lambda2+16*lambda2)) / 8 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.Values.Push(filtered)
inc.lastMeasurement = value inc.lastMeasurement = value
} }
func (inc *GHFilter) Index(i int) float64 { func (inc *GHFilter) Index(i int) float64 {
if inc.Values == nil { return inc.Last(i)
return 0.0
}
return inc.Values.Index(i)
} }
func (inc *GHFilter) Length() int { func (inc *GHFilter) Length() int {
@ -58,11 +56,11 @@ func (inc *GHFilter) Length() int {
return inc.Values.Length() return inc.Values.Length()
} }
func (inc *GHFilter) Last() float64 { func (inc *GHFilter) Last(i int) float64 {
if inc.Values == nil { if inc.Values == nil {
return 0.0 return 0.0
} }
return inc.Values.Last() return inc.Values.Last(i)
} }
// interfaces implementation check // interfaces implementation check

View File

@ -6058,7 +6058,7 @@ func Test_GHFilter(t *testing.T) {
for _, k := range klines { for _, k := range klines {
filter.PushK(k) filter.PushK(k)
} }
got := filter.Last() got := filter.Last(0)
got = math.Trunc(got*100.0) / 100.0 got = math.Trunc(got*100.0) / 100.0
if got != tt.want { if got != tt.want {
t.Errorf("GHFilter.Last() = %v, want %v", 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 { for i, k := range klines {
// square error between last estimated state and current actual state // square error between last estimated state and current actual state
if i > 0 { if i > 0 {
filterDiff2Sum += klineSquareError(filter.Last(), k) filterDiff2Sum += klineSquareError(filter.Last(0), k)
ewmaDiff2Sum += klineSquareError(ewma.Last(), k) ewmaDiff2Sum += klineSquareError(ewma.Last(0), k)
filterCloseDiff2Sum += closeSquareError(filter.Last(), k) filterCloseDiff2Sum += closeSquareError(filter.Last(0), k)
ewmaCloseDiff2Sum += closeSquareError(ewma.Last(), k) ewmaCloseDiff2Sum += closeSquareError(ewma.Last(0), k)
} }
// update estimations // update estimations

View File

@ -22,18 +22,15 @@ type GMA struct {
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
func (inc *GMA) Last() float64 { func (inc *GMA) Last(i int) float64 {
if inc.SMA == nil { if inc.SMA == nil {
return 0.0 return 0.0
} }
return math.Exp(inc.SMA.Last()) return math.Exp(inc.SMA.Last(i))
} }
func (inc *GMA) Index(i int) float64 { func (inc *GMA) Index(i int) float64 {
if inc.SMA == nil { return inc.Last(i)
return 0.0
}
return math.Exp(inc.SMA.Index(i))
} }
func (inc *GMA) Length() int { func (inc *GMA) Length() int {

View File

@ -51,10 +51,10 @@ func Test_GMA(t *testing.T) {
for _, k := range tt.kLines { for _, k := range tt.kLines {
gma.PushK(k) 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) assert.InDelta(t, tt.next, gma.Index(1), Delta)
gma.Update(tt.update) 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()) assert.Equal(t, tt.all, gma.Length())
}) })
} }

View File

@ -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 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 // 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. // forms the HMA line, which can be used to make predictions about future price movements.
//
//go:generate callbackgen -type HULL //go:generate callbackgen -type HULL
type HULL struct { type HULL struct {
types.SeriesBase types.SeriesBase
@ -36,14 +37,14 @@ func (inc *HULL) Update(value float64) {
} }
inc.ma1.Update(value) inc.ma1.Update(value)
inc.ma2.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 { if inc.result == nil {
return 0 return 0
} }
return inc.result.Last() return inc.result.Index(i)
} }
func (inc *HULL) Index(i int) float64 { func (inc *HULL) Index(i int) float64 {
@ -66,5 +67,5 @@ func (inc *HULL) PushK(k types.KLine) {
} }
inc.Update(k.Close.Float64()) inc.Update(k.Close.Float64())
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }

View File

@ -51,7 +51,7 @@ func Test_HULL(t *testing.T) {
hull.PushK(k) hull.PushK(k)
} }
last := hull.Last() last := hull.Last(0)
assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.want, last, Delta)
assert.InDelta(t, tt.next, hull.Index(1), Delta) assert.InDelta(t, tt.next, hull.Index(1), Delta)
assert.Equal(t, tt.all, hull.Length()) assert.Equal(t, tt.all, hull.Length())

View File

@ -25,7 +25,7 @@ type KLinePusher interface {
// Simple is the simple indicator that only returns one float64 value // Simple is the simple indicator that only returns one float64 value
type Simple interface { type Simple interface {
KLinePusher KLinePusher
Last() float64 Last(int) float64
OnUpdate(f func(value float64)) OnUpdate(f func(value float64))
} }

View File

@ -1,9 +1,10 @@
package indicator package indicator
import ( import (
"math"
"github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/datatype/floats"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
"math"
) )
// Refer: https://www.kalmanfilter.net/kalman1d.html // Refer: https://www.kalmanfilter.net/kalman1d.html
@ -25,7 +26,7 @@ type KalmanFilter struct {
func (inc *KalmanFilter) Update(value float64) { func (inc *KalmanFilter) Update(value float64) {
var measureMove = value var measureMove = value
if inc.measurements != nil { if inc.measurements != nil {
measureMove = value - inc.measurements.Last() measureMove = value - inc.measurements.Last(0)
} }
inc.update(value, math.Abs(measureMove)) 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) q := math.Sqrt(types.Mean(inc.amp2)) * float64(1+inc.AdditionalSmoothWindow)
// update // update
lastPredict := inc.Values.Last() lastPredict := inc.Values.Last(0)
curState := value + (value - lastPredict) curState := value + (value - lastPredict)
estimated := lastPredict + inc.k*(curState-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 { func (inc *KalmanFilter) Index(i int) float64 {
if inc.Values == nil { return inc.Last(i)
return 0.0
}
return inc.Values.Index(i)
} }
func (inc *KalmanFilter) Length() int { func (inc *KalmanFilter) Length() int {
if inc.Values == nil {
return 0
}
return inc.Values.Length() return inc.Values.Length()
} }
func (inc *KalmanFilter) Last() float64 { func (inc *KalmanFilter) Last(i int) float64 {
if inc.Values == nil { return inc.Values.Last(i)
return 0.0
}
return inc.Values.Last()
} }
// interfaces implementation check // interfaces implementation check

View File

@ -6065,7 +6065,7 @@ func Test_KalmanFilter(t *testing.T) {
for _, k := range klines { for _, k := range klines {
filter.PushK(k) filter.PushK(k)
} }
got := filter.Last() got := filter.Last(0)
got = math.Trunc(got*100.0) / 100.0 got = math.Trunc(got*100.0) / 100.0
if got != tt.want { if got != tt.want {
t.Errorf("KalmanFilter.Last() = %v, want %v", 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 { for _, k := range klines {
// square error between last estimated state and current actual state // square error between last estimated state and current actual state
if ewma.Length() > 0 { if ewma.Length() > 0 {
filterDiff2Sum += klineSquareError(filter.Last(), k) filterDiff2Sum += klineSquareError(filter.Last(0), k)
ewmaDiff2Sum += klineSquareError(ewma.Last(), k) ewmaDiff2Sum += klineSquareError(ewma.Last(0), k)
filterCloseDiff2Sum += closeSquareError(filter.Last(), k) filterCloseDiff2Sum += closeSquareError(filter.Last(0), k)
ewmaCloseDiff2Sum += closeSquareError(ewma.Last(), k) ewmaCloseDiff2Sum += closeSquareError(ewma.Last(0), k)
numEstimations++ numEstimations++
} }

View File

@ -12,6 +12,7 @@ import (
// The Klinger Oscillator is calculated by taking the difference between a 34-period and 55-period moving average. // 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. // 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. // 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 //go:generate callbackgen -type KlingerOscillator
type KlingerOscillator struct { type KlingerOscillator struct {
types.SeriesBase types.SeriesBase
@ -30,17 +31,11 @@ func (inc *KlingerOscillator) Length() int {
return inc.Fast.Length() return inc.Fast.Length()
} }
func (inc *KlingerOscillator) Last() float64 { func (inc *KlingerOscillator) Last(i int) float64 {
if inc.Fast == nil || inc.Slow == nil { if inc.Fast == nil || inc.Slow == nil {
return 0 return 0
} }
return inc.Fast.Last() - inc.Slow.Last() return inc.Fast.Last(i) - inc.Slow.Last(i)
}
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)
} }
func (inc *KlingerOscillator) Update(high, low, cloze, volume float64) { func (inc *KlingerOscillator) Update(high, low, cloze, volume float64) {

View File

@ -38,12 +38,12 @@ func (l *Line) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(l.handleKLineWindowUpdate) updater.OnKLineWindowUpdate(l.handleKLineWindowUpdate)
} }
func (l *Line) Last() float64 { func (l *Line) Last(i int) float64 {
return (l.end-l.start)/float64(l.startIndex-l.endIndex)*float64(l.endIndex) + l.end return (l.end-l.start)/float64(l.startIndex-l.endIndex)*float64(l.endIndex-i) + l.end
} }
func (l *Line) Index(i int) float64 { 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 { func (l *Line) Length() int {

View File

@ -1,9 +1,10 @@
package indicator package indicator
import ( import (
"github.com/sirupsen/logrus"
"time" "time"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/datatype/floats"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
@ -11,6 +12,7 @@ import (
var logLinReg = logrus.WithField("indicator", "LinReg") var logLinReg = logrus.WithField("indicator", "LinReg")
// LinReg is Linear Regression baseline // LinReg is Linear Regression baseline
//
//go:generate callbackgen -type LinReg //go:generate callbackgen -type LinReg
type LinReg struct { type LinReg struct {
types.SeriesBase types.SeriesBase
@ -28,11 +30,8 @@ type LinReg struct {
} }
// Last slope of linear regression baseline // Last slope of linear regression baseline
func (lr *LinReg) Last() float64 { func (lr *LinReg) Last(i int) float64 {
if lr.Values.Length() == 0 { return lr.Values.Last(i)
return 0.0
}
return lr.Values.Last()
} }
// LastRatio of slope to price // LastRatio of slope to price
@ -40,16 +39,12 @@ func (lr *LinReg) LastRatio() float64 {
if lr.ValueRatios.Length() == 0 { if lr.ValueRatios.Length() == 0 {
return 0.0 return 0.0
} }
return lr.ValueRatios.Last() return lr.ValueRatios.Last(0)
} }
// Index returns the slope of specified index // Index returns the slope of specified index
func (lr *LinReg) Index(i int) float64 { func (lr *LinReg) Index(i int) float64 {
if i >= lr.Values.Length() { return lr.Values.Last(i)
return 0.0
}
return lr.Values.Index(i)
} }
// IndexRatio returns the slope ratio // IndexRatio returns the slope ratio
@ -58,7 +53,7 @@ func (lr *LinReg) IndexRatio(i int) float64 {
return 0.0 return 0.0
} }
return lr.ValueRatios.Index(i) return lr.ValueRatios.Last(i)
} }
// Length of the slope values // Length of the slope values
@ -99,9 +94,9 @@ func (lr *LinReg) Update(kline types.KLine) {
endPrice := average - slope*sumX/length + slope endPrice := average - slope*sumX/length + slope
startPrice := endPrice + slope*(length-1) startPrice := endPrice + slope*(length-1)
lr.Values.Push((endPrice - startPrice) / (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) { func (lr *LinReg) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) {

View File

@ -33,5 +33,5 @@ func (inc *Low) PushK(k types.KLine) {
inc.Update(k.Low.Float64()) inc.Update(k.Low.Float64())
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }

View File

@ -59,14 +59,14 @@ func (inc *MACDLegacy) Update(x float64) {
inc.slowEWMA.Update(x) inc.slowEWMA.Update(x)
// update MACD value, it's also the signal line // update MACD value, it's also the signal line
fast := inc.fastEWMA.Last() fast := inc.fastEWMA.Last(0)
slow := inc.slowEWMA.Last() slow := inc.slowEWMA.Last(0)
macd := fast - slow macd := fast - slow
inc.Values.Push(macd) inc.Values.Push(macd)
// update signal line // update signal line
inc.signalLine.Update(macd) inc.signalLine.Update(macd)
signal := inc.signalLine.Last() signal := inc.signalLine.Last(0)
// update histogram // update histogram
histogram := macd - signal histogram := macd - signal
@ -75,7 +75,7 @@ func (inc *MACDLegacy) Update(x float64) {
inc.EmitUpdate(macd, signal, histogram) inc.EmitUpdate(macd, signal, histogram)
} }
func (inc *MACDLegacy) Last() float64 { func (inc *MACDLegacy) Last(int) float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0.0 return 0.0
} }
@ -106,21 +106,12 @@ type MACDValues struct {
*MACDLegacy *MACDLegacy
} }
func (inc *MACDValues) Last() float64 { func (inc *MACDValues) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0.0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *MACDValues) Index(i int) float64 { func (inc *MACDValues) Index(i int) float64 {
length := len(inc.Values) return inc.Values.Last(i)
if length == 0 || length-1-i < 0 {
return 0.0
}
return inc.Values[length-1+i]
} }
func (inc *MACDValues) Length() int { func (inc *MACDValues) Length() int {

View File

@ -45,7 +45,7 @@ func Test_calculateMACD(t *testing.T) {
macd.PushK(k) macd.PushK(k)
} }
got := macd.Last() got := macd.Last(0)
diff := math.Trunc((got-tt.want)*100) / 100 diff := math.Trunc((got-tt.want)*100) / 100
if diff != 0 { if diff != 0 {
t.Errorf("calculateMACD() = %v, want %v", got, tt.want) t.Errorf("calculateMACD() = %v, want %v", got, tt.want)

View File

@ -40,24 +40,18 @@ func (inc *OBV) Update(price, volume float64) {
} }
if volume < inc.PrePrice { if volume < inc.PrePrice {
inc.Values.Push(inc.Last() - volume) inc.Values.Push(inc.Last(0) - volume)
} else { } else {
inc.Values.Push(inc.Last() + volume) inc.Values.Push(inc.Last(0) + volume)
} }
} }
func (inc *OBV) Last() float64 { func (inc *OBV) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0.0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *OBV) Index(i int) float64 { func (inc *OBV) Index(i int) float64 {
if len(inc.Values)-i <= 0 { return inc.Last(i)
return 0.0
}
return inc.Values[len(inc.Values)-i-1]
} }
var _ types.SeriesExtend = &OBV{} var _ types.SeriesExtend = &OBV{}
@ -75,7 +69,7 @@ func (inc *OBV) CalculateAndUpdate(kLines []types.KLine) {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
inc.EndTime = kLines[len(kLines)-1].EndTime.Time() inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
} }

View File

@ -10,7 +10,6 @@ import (
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
//go:generate callbackgen -type Pivot //go:generate callbackgen -type Pivot
type Pivot struct { type Pivot struct {
types.IntervalWindow types.IntervalWindow
@ -105,12 +104,12 @@ func calculatePivot(klines []types.KLine, window int, valLow KLineValueMapper, v
} }
pl := 0. pl := 0.
if lows.Min() == lows.Index(int(window/2.)-1) { if lows.Min() == lows.Last(int(window/2.)-1) {
pl = lows.Min() pl = lows.Min()
} }
ph := 0. ph := 0.
if highs.Max() == highs.Index(int(window/2.)-1) { if highs.Max() == highs.Last(int(window/2.)-1) {
ph = highs.Max() ph = highs.Max()
} }

View File

@ -24,12 +24,12 @@ func (inc *PivotHigh) Length() int {
return inc.Values.Length() return inc.Values.Length()
} }
func (inc *PivotHigh) Last() float64 { func (inc *PivotHigh) Last(int) float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0.0 return 0.0
} }
return inc.Values.Last() return inc.Values.Last(0)
} }
func (inc *PivotHigh) Update(value float64) { func (inc *PivotHigh) Update(value float64) {
@ -60,5 +60,5 @@ func (inc *PivotHigh) PushK(k types.KLine) {
inc.Update(k.High.Float64()) inc.Update(k.High.Float64())
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }

View File

@ -24,12 +24,12 @@ func (inc *PivotLow) Length() int {
return inc.Values.Length() return inc.Values.Length()
} }
func (inc *PivotLow) Last() float64 { func (inc *PivotLow) Last(int) float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0.0 return 0.0
} }
return inc.Values.Last() return inc.Values.Last(0)
} }
func (inc *PivotLow) Update(value float64) { func (inc *PivotLow) Update(value float64) {
@ -60,7 +60,7 @@ func (inc *PivotLow) PushK(k types.KLine) {
inc.Update(k.Low.Float64()) inc.Update(k.Low.Float64())
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
func calculatePivotHigh(highs floats.Slice, left, right int) (float64, bool) { func calculatePivotHigh(highs floats.Slice, left, right int) (float64, bool) {

View File

@ -34,11 +34,11 @@ type PSAR struct {
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
func (inc *PSAR) Last() float64 { func (inc *PSAR) Last(int) float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0 return 0
} }
return inc.Values.Last() return inc.Values.Last(0)
} }
func (inc *PSAR) Length() int { func (inc *PSAR) Length() int {
@ -46,8 +46,8 @@ func (inc *PSAR) Length() int {
} }
func (inc *PSAR) falling() bool { func (inc *PSAR) falling() bool {
up := inc.High.Last() - inc.High.Index(1) up := inc.High.Last(0) - inc.High.Index(1)
dn := inc.Low.Index(1) - inc.Low.Last() dn := inc.Low.Index(1) - inc.Low.Last(0)
return (dn > up) && (dn > 0) return (dn > up) && (dn > 0)
} }
@ -66,7 +66,7 @@ func (inc *PSAR) Update(high, low float64) {
inc.High.Update(high) inc.High.Update(high)
inc.Low.Update(low) inc.Low.Update(low)
if !isFirst { if !isFirst {
ppsar := inc.Values.Last() ppsar := inc.Values.Last(0)
if inc.Falling { // falling formula if inc.Falling { // falling formula
psar := ppsar - inc.AF*(ppsar-inc.EP) psar := ppsar - inc.AF*(ppsar-inc.EP)
h := inc.High.Shift(1).Highest(2) h := inc.High.Shift(1).Highest(2)

View File

@ -36,5 +36,5 @@ func Test_PSAR(t *testing.T) {
} }
assert.Equal(t, psar.Length(), 29) assert.Equal(t, psar.Length(), 29)
assert.Equal(t, psar.AF, 0.04) assert.Equal(t, psar.AF, 0.04)
assert.Equal(t, psar.Last(), 0.16) assert.Equal(t, psar.Last(0), 0.16)
} }

View File

@ -78,16 +78,12 @@ func (inc *RMA) Update(x float64) {
} }
} }
func (inc *RMA) Last() float64 { func (inc *RMA) Last(i int) float64 {
return inc.Values.Last() return inc.Values.Last(i)
} }
func (inc *RMA) Index(i int) float64 { func (inc *RMA) Index(i int) float64 {
length := len(inc.Values) return inc.Last(i)
if length == 0 || length-i-1 < 0 {
return 0
}
return inc.Values[length-i-1]
} }
func (inc *RMA) Length() int { func (inc *RMA) Length() int {
@ -116,7 +112,7 @@ func (inc *RMA) CalculateAndUpdate(kLines []types.KLine) {
inc.PushK(last) inc.PushK(last)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
func (inc *RMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { func (inc *RMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {

View File

@ -65,19 +65,12 @@ func (inc *RSI) Update(price float64) {
inc.PreviousAvgLoss = avgLoss inc.PreviousAvgLoss = avgLoss
} }
func (inc *RSI) Last() float64 { func (inc *RSI) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0.0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *RSI) Index(i int) float64 { func (inc *RSI) Index(i int) float64 {
length := len(inc.Values) return inc.Last(i)
if length <= 0 || length-i-1 < 0 {
return 0.0
}
return inc.Values[length-i-1]
} }
func (inc *RSI) Length() int { func (inc *RSI) Length() int {
@ -99,7 +92,7 @@ func (inc *RSI) CalculateAndUpdate(kLines []types.KLine) {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
inc.EndTime = kLines[len(kLines)-1].EndTime.Time() inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
} }

View File

@ -22,19 +22,12 @@ type SMA struct {
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
func (inc *SMA) Last() float64 { func (inc *SMA) Last(i int) float64 {
if inc.Values.Length() == 0 { return inc.Values.Last(i)
return 0.0
}
return inc.Values.Last()
} }
func (inc *SMA) Index(i int) float64 { func (inc *SMA) Index(i int) float64 {
if i >= inc.Values.Length() { return inc.Last(i)
return 0.0
}
return inc.Values.Index(i)
} }
func (inc *SMA) Length() int { func (inc *SMA) Length() int {
@ -81,7 +74,7 @@ func (inc *SMA) PushK(k types.KLine) {
inc.Update(k.Close.Float64()) inc.Update(k.Close.Float64())
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Values.Last()) inc.EmitUpdate(inc.Values.Last(0))
} }
func (inc *SMA) LoadK(allKLines []types.KLine) { func (inc *SMA) LoadK(allKLines []types.KLine) {

View File

@ -58,10 +58,10 @@ func Test_SMA(t *testing.T) {
sma.PushK(k) 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) assert.InDelta(t, tt.next, sma.Index(1), Delta)
sma.Update(tt.update) 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()) assert.Equal(t, tt.all, sma.Length())
}) })
} }

View File

@ -50,9 +50,9 @@ func (inc *SSF) Update(value float64) {
} }
result := inc.c1*value + result := inc.c1*value +
inc.c2*inc.Values.Index(0) + inc.c2*inc.Values.Last(0) +
inc.c3*inc.Values.Index(1) + inc.c3*inc.Values.Last(1) +
inc.c4*inc.Values.Index(2) inc.c4*inc.Values.Last(2)
inc.Values.Push(result) inc.Values.Push(result)
} else { // poles == 2 } else { // poles == 2
if inc.Values == nil { if inc.Values == nil {
@ -65,17 +65,18 @@ func (inc *SSF) Update(value float64) {
inc.Values = floats.Slice{} inc.Values = floats.Slice{}
} }
result := inc.c1*value + result := inc.c1*value +
inc.c2*inc.Values.Index(0) + inc.c2*inc.Values.Last(0) +
inc.c3*inc.Values.Index(1) inc.c3*inc.Values.Last(1)
inc.Values.Push(result) inc.Values.Push(result)
} }
} }
func (inc *SSF) Last(i int) float64 {
return inc.Values.Last(i)
}
func (inc *SSF) Index(i int) float64 { func (inc *SSF) Index(i int) float64 {
if inc.Values == nil { return inc.Last(i)
return 0.0
}
return inc.Values.Index(i)
} }
func (inc *SSF) Length() int { func (inc *SSF) Length() int {
@ -85,13 +86,6 @@ func (inc *SSF) Length() int {
return inc.Values.Length() return inc.Values.Length()
} }
func (inc *SSF) Last() float64 {
if inc.Values == nil {
return 0.0
}
return inc.Values.Last()
}
var _ types.SeriesExtend = &SSF{} var _ types.SeriesExtend = &SSF{}
func (inc *SSF) PushK(k types.KLine) { func (inc *SSF) PushK(k types.KLine) {
@ -102,12 +96,12 @@ func (inc *SSF) CalculateAndUpdate(allKLines []types.KLine) {
if inc.Values != nil { if inc.Values != nil {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
return return
} }
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -63,7 +63,7 @@ func Test_SSF(t *testing.T) {
Poles: tt.poles, Poles: tt.poles,
} }
ssf.CalculateAndUpdate(tt.kLines) 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.InDelta(t, tt.next, ssf.Index(1), Delta)
assert.Equal(t, tt.all, ssf.Length()) assert.Equal(t, tt.all, ssf.Length())
}) })

View File

@ -21,19 +21,12 @@ type StdDev struct {
updateCallbacks []func(value float64) updateCallbacks []func(value float64)
} }
func (inc *StdDev) Last() float64 { func (inc *StdDev) Last(i int) float64 {
if inc.Values.Length() == 0 { return inc.Values.Last(i)
return 0.0
}
return inc.Values.Last()
} }
func (inc *StdDev) Index(i int) float64 { func (inc *StdDev) Index(i int) float64 {
if i >= inc.Values.Length() { return inc.Last(i)
return 0.0
}
return inc.Values.Index(i)
} }
func (inc *StdDev) Length() int { func (inc *StdDev) Length() int {
@ -76,7 +69,7 @@ func (inc *StdDev) CalculateAndUpdate(allKLines []types.KLine) {
inc.PushK(last) inc.PushK(last)
} }
inc.EmitUpdate(inc.Values.Last()) inc.EmitUpdate(inc.Values.Last(0))
} }
func (inc *StdDev) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { func (inc *StdDev) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {

View File

@ -50,16 +50,12 @@ type Supertrend struct {
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
func (inc *Supertrend) Last() float64 { func (inc *Supertrend) Last(i int) float64 {
return inc.trendPrices.Last() return inc.trendPrices.Last(i)
} }
func (inc *Supertrend) Index(i int) float64 { func (inc *Supertrend) Index(i int) float64 {
length := inc.Length() return inc.Last(i)
if length == 0 || length-i-1 < 0 {
return 0
}
return inc.trendPrices[length-i-1]
} }
func (inc *Supertrend) Length() int { func (inc *Supertrend) Length() int {
@ -94,13 +90,13 @@ func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) {
src := (highPrice + lowPrice) / 2 src := (highPrice + lowPrice) / 2
// Update uptrend // Update uptrend
inc.uptrendPrice = src - inc.AverageTrueRange.Last()*inc.ATRMultiplier inc.uptrendPrice = src - inc.AverageTrueRange.Last(0)*inc.ATRMultiplier
if inc.previousClosePrice > inc.previousUptrendPrice { if inc.previousClosePrice > inc.previousUptrendPrice {
inc.uptrendPrice = math.Max(inc.uptrendPrice, inc.previousUptrendPrice) inc.uptrendPrice = math.Max(inc.uptrendPrice, inc.previousUptrendPrice)
} }
// Update downtrend // Update downtrend
inc.downtrendPrice = src + inc.AverageTrueRange.Last()*inc.ATRMultiplier inc.downtrendPrice = src + inc.AverageTrueRange.Last(0)*inc.ATRMultiplier
if inc.previousClosePrice < inc.previousDowntrendPrice { if inc.previousClosePrice < inc.previousDowntrendPrice {
inc.downtrendPrice = math.Min(inc.downtrendPrice, inc.previousDowntrendPrice) inc.downtrendPrice = math.Min(inc.downtrendPrice, inc.previousDowntrendPrice)
} }
@ -115,7 +111,7 @@ func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) {
} }
// Update signal // Update signal
if inc.AverageTrueRange.Last() <= 0 { if inc.AverageTrueRange.Last(0) <= 0 {
inc.tradeSignal = types.DirectionNone inc.tradeSignal = types.DirectionNone
} else if inc.trend == types.DirectionUp && inc.previousTrend == types.DirectionDown { } else if inc.trend == types.DirectionUp && inc.previousTrend == types.DirectionDown {
inc.tradeSignal = types.DirectionUp 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,"+ logst.Debugf("Update supertrend result: closePrice: %v, uptrendPrice: %v, downtrendPrice: %v, trend: %v,"+
" tradeSignal: %v, AverageTrueRange.Last(): %v", inc.closePrice, inc.uptrendPrice, inc.downtrendPrice, " 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 { func (inc *Supertrend) GetSignal() types.Direction {
@ -152,12 +148,12 @@ func (inc *Supertrend) Direction() types.Direction {
// LastSupertrendSupport return the current supertrend support // LastSupertrendSupport return the current supertrend support
func (inc *Supertrend) LastSupertrendSupport() float64 { func (inc *Supertrend) LastSupertrendSupport() float64 {
return inc.supportLine.Last() return inc.supportLine.Last(0)
} }
// LastSupertrendResistance return the current supertrend resistance // LastSupertrendResistance return the current supertrend resistance
func (inc *Supertrend) LastSupertrendResistance() float64 { func (inc *Supertrend) LastSupertrendResistance() float64 {
return inc.resistanceLine.Last() return inc.resistanceLine.Last(0)
} }
var _ types.SeriesExtend = &Supertrend{} 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.Update(k.GetHigh().Float64(), k.GetLow().Float64(), k.GetClose().Float64())
inc.EndTime = k.EndTime.Time() 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.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
inc.EndTime = kLines[len(kLines)-1].EndTime.Time() inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
} }

View File

@ -50,16 +50,12 @@ type PivotSupertrend struct {
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
func (inc *PivotSupertrend) Last() float64 { func (inc *PivotSupertrend) Last(i int) float64 {
return inc.trendPrices.Last() return inc.trendPrices.Last(i)
} }
func (inc *PivotSupertrend) Index(i int) float64 { func (inc *PivotSupertrend) Index(i int) float64 {
length := inc.Length() return inc.Last(i)
if length == 0 || length-i-1 < 0 {
return 0
}
return inc.trendPrices[length-i-1]
} }
func (inc *PivotSupertrend) Length() int { func (inc *PivotSupertrend) Length() int {
@ -80,8 +76,8 @@ func (inc *PivotSupertrend) Update(highPrice, lowPrice, closePrice float64) {
inc.trend = types.DirectionUp inc.trend = types.DirectionUp
} }
inc.previousPivotLow = inc.PivotLow.Last() inc.previousPivotLow = inc.PivotLow.Last(0)
inc.previousPivotHigh = inc.PivotHigh.Last() inc.previousPivotHigh = inc.PivotHigh.Last(0)
// Update High / Low pivots // Update High / Low pivots
inc.PivotLow.Update(lowPrice) inc.PivotLow.Update(lowPrice)
@ -101,9 +97,9 @@ func (inc *PivotSupertrend) Update(highPrice, lowPrice, closePrice float64) {
// Initialize lastPp as soon as pivots are made // Initialize lastPp as soon as pivots are made
if inc.lastPp == 0 || math.IsNaN(inc.lastPp) { if inc.lastPp == 0 || math.IsNaN(inc.lastPp) {
if inc.PivotHigh.Length() > 0 { if inc.PivotHigh.Length() > 0 {
inc.lastPp = inc.PivotHigh.Last() inc.lastPp = inc.PivotHigh.Last(0)
} else if inc.PivotLow.Length() > 0 { } else if inc.PivotLow.Length() > 0 {
inc.lastPp = inc.PivotLow.Last() inc.lastPp = inc.PivotLow.Last(0)
} else { } else {
inc.lastPp = math.NaN() inc.lastPp = math.NaN()
return 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) // Set lastPp to the latest pivotPoint (only changed when new pivot is found)
if inc.PivotHigh.Last() != inc.previousPivotHigh { if inc.PivotHigh.Last(0) != inc.previousPivotHigh {
inc.lastPp = inc.PivotHigh.Last() inc.lastPp = inc.PivotHigh.Last(0)
} else if inc.PivotLow.Last() != inc.previousPivotLow { } else if inc.PivotLow.Last(0) != inc.previousPivotLow {
inc.lastPp = inc.PivotLow.Last() inc.lastPp = inc.PivotLow.Last(0)
} }
// calculate the Center line using pivot points // calculate the Center line using pivot points
if inc.src == 0 || math.IsNaN(inc.src) { if inc.src == 0 || math.IsNaN(inc.src) {
inc.src = inc.lastPp inc.src = inc.lastPp
} else { } else {
//weighted calculation // weighted calculation
inc.src = (inc.src*2 + inc.lastPp) / 3 inc.src = (inc.src*2 + inc.lastPp) / 3
} }
// Update uptrend // 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 { if inc.previousClosePrice > inc.previousUptrendPrice {
inc.uptrendPrice = math.Max(inc.uptrendPrice, inc.previousUptrendPrice) inc.uptrendPrice = math.Max(inc.uptrendPrice, inc.previousUptrendPrice)
} }
// Update downtrend // 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 { if inc.previousClosePrice < inc.previousDowntrendPrice {
inc.downtrendPrice = math.Min(inc.downtrendPrice, inc.previousDowntrendPrice) inc.downtrendPrice = math.Min(inc.downtrendPrice, inc.previousDowntrendPrice)
} }
@ -147,7 +143,7 @@ func (inc *PivotSupertrend) Update(highPrice, lowPrice, closePrice float64) {
} }
// Update signal // Update signal
if inc.AverageTrueRange.Last() <= 0 { if inc.AverageTrueRange.Last(0) <= 0 {
inc.tradeSignal = types.DirectionNone inc.tradeSignal = types.DirectionNone
} else if inc.trend == types.DirectionUp && inc.previousTrend == types.DirectionDown { } else if inc.trend == types.DirectionUp && inc.previousTrend == types.DirectionDown {
inc.tradeSignal = types.DirectionUp 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,"+ 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, " 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) // GetSignal returns signal (Down, None or Up)
@ -185,12 +181,12 @@ func (inc *PivotSupertrend) Direction() types.Direction {
// LastSupertrendSupport return the current supertrend support value // LastSupertrendSupport return the current supertrend support value
func (inc *PivotSupertrend) LastSupertrendSupport() float64 { func (inc *PivotSupertrend) LastSupertrendSupport() float64 {
return inc.supportLine.Last() return inc.supportLine.Last(0)
} }
// LastSupertrendResistance return the current supertrend resistance value // LastSupertrendResistance return the current supertrend resistance value
func (inc *PivotSupertrend) LastSupertrendResistance() float64 { func (inc *PivotSupertrend) LastSupertrendResistance() float64 {
return inc.resistanceLine.Last() return inc.resistanceLine.Last(0)
} }
var _ types.SeriesExtend = &PivotSupertrend{} 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.Update(k.GetHigh().Float64(), k.GetLow().Float64(), k.GetClose().Float64())
inc.EndTime = k.EndTime.Time() 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) { func (inc *PivotSupertrend) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) {

View File

@ -36,26 +36,20 @@ func (inc *TEMA) Update(value float64) {
inc.A3 = &EWMA{IntervalWindow: inc.IntervalWindow} inc.A3 = &EWMA{IntervalWindow: inc.IntervalWindow}
} }
inc.A1.Update(value) inc.A1.Update(value)
a1 := inc.A1.Last() a1 := inc.A1.Last(0)
inc.A2.Update(a1) inc.A2.Update(a1)
a2 := inc.A2.Last() a2 := inc.A2.Last(0)
inc.A3.Update(a2) inc.A3.Update(a2)
a3 := inc.A3.Last() a3 := inc.A3.Last(0)
inc.Values.Push(3*a1 - 3*a2 + a3) inc.Values.Push(3*a1 - 3*a2 + a3)
} }
func (inc *TEMA) Last() float64 { func (inc *TEMA) Last(i int) float64 {
if len(inc.Values) > 0 { return inc.Values.Last(i)
return inc.Values[len(inc.Values)-1]
}
return 0.0
} }
func (inc *TEMA) Index(i int) float64 { func (inc *TEMA) Index(i int) float64 {
if i >= len(inc.Values) { return inc.Last(i)
return 0
}
return inc.Values[len(inc.Values)-i-1]
} }
func (inc *TEMA) Length() int { func (inc *TEMA) Length() int {
@ -72,12 +66,12 @@ func (inc *TEMA) CalculateAndUpdate(allKLines []types.KLine) {
if inc.A1 == nil { if inc.A1 == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -47,7 +47,7 @@ func Test_TEMA(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tema := TEMA{IntervalWindow: types.IntervalWindow{Window: 16}} tema := TEMA{IntervalWindow: types.IntervalWindow{Window: 16}}
tema.CalculateAndUpdate(tt.kLines) tema.CalculateAndUpdate(tt.kLines)
last := tema.Last() last := tema.Last(0)
assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.want, last, Delta)
assert.InDelta(t, tt.next, tema.Index(1), Delta) assert.InDelta(t, tt.next, tema.Index(1), Delta)
assert.Equal(t, tt.all, tema.Length()) assert.Equal(t, tt.all, tema.Length())

View File

@ -57,28 +57,18 @@ func (inc *TILL) Update(value float64) {
} }
inc.e1.Update(value) inc.e1.Update(value)
inc.e2.Update(inc.e1.Last()) inc.e2.Update(inc.e1.Last(0))
inc.e3.Update(inc.e2.Last()) inc.e3.Update(inc.e2.Last(0))
inc.e4.Update(inc.e3.Last()) inc.e4.Update(inc.e3.Last(0))
inc.e5.Update(inc.e4.Last()) inc.e5.Update(inc.e4.Last(0))
inc.e6.Update(inc.e5.Last()) inc.e6.Update(inc.e5.Last(0))
} }
func (inc *TILL) Last() float64 { func (inc *TILL) Last(i int) 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 {
if inc.e1 == nil || inc.e1.Length() <= i { if inc.e1 == nil || inc.e1.Length() <= i {
return 0 return 0
} }
e3 := inc.e3.Index(i) e3 := inc.e3.Index(i)
e4 := inc.e4.Index(i) e4 := inc.e4.Index(i)
e5 := inc.e5.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 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 { func (inc *TILL) Length() int {
if inc.e1 == nil { if inc.e1 == nil {
return 0 return 0
@ -101,7 +95,7 @@ func (inc *TILL) PushK(k types.KLine) {
} }
inc.Update(k.Close.Float64()) inc.Update(k.Close.Float64())
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
func (inc *TILL) LoadK(allKLines []types.KLine) { func (inc *TILL) LoadK(allKLines []types.KLine) {

View File

@ -57,7 +57,7 @@ func Test_TILL(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
till := TILL{IntervalWindow: types.IntervalWindow{Window: 16}} till := TILL{IntervalWindow: types.IntervalWindow{Window: 16}}
till.CalculateAndUpdate(tt.kLines) till.CalculateAndUpdate(tt.kLines)
last := till.Last() last := till.Last(0)
assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.want, last, Delta)
assert.InDelta(t, tt.next, till.Index(1), Delta) assert.InDelta(t, tt.next, till.Index(1), Delta)
assert.Equal(t, tt.all, till.Length()) assert.Equal(t, tt.all, till.Length())

View File

@ -6,6 +6,7 @@ import (
// Refer: Triangular Moving Average // Refer: Triangular Moving Average
// Refer URL: https://ja.wikipedia.org/wiki/移動平均 // Refer URL: https://ja.wikipedia.org/wiki/移動平均
//
//go:generate callbackgen -type TMA //go:generate callbackgen -type TMA
type TMA struct { type TMA struct {
types.SeriesBase types.SeriesBase
@ -24,21 +25,15 @@ func (inc *TMA) Update(value float64) {
} }
inc.s1.Update(value) inc.s1.Update(value)
inc.s2.Update(inc.s1.Last()) inc.s2.Update(inc.s1.Last(0))
} }
func (inc *TMA) Last() float64 { func (inc *TMA) Last(i int) float64 {
if inc.s2 == nil { return inc.s2.Last(i)
return 0
}
return inc.s2.Last()
} }
func (inc *TMA) Index(i int) float64 { func (inc *TMA) Index(i int) float64 {
if inc.s2 == nil { return inc.Last(i)
return 0
}
return inc.s2.Index(i)
} }
func (inc *TMA) Length() int { func (inc *TMA) Length() int {
@ -58,12 +53,12 @@ func (inc *TMA) CalculateAndUpdate(allKLines []types.KLine) {
if inc.s1 == nil { if inc.s1 == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -9,6 +9,7 @@ import (
// Refer: True Strength Index // Refer: True Strength Index
// Refer URL: https://www.investopedia.com/terms/t/tsi.asp // Refer URL: https://www.investopedia.com/terms/t/tsi.asp
//
//go:generate callbackgen -type TSI //go:generate callbackgen -type TSI
type TSI struct { type TSI struct {
types.SeriesBase types.SeriesBase
@ -66,10 +67,10 @@ func (inc *TSI) Update(value float64) {
apc := math.Abs(pc) apc := math.Abs(pc)
inc.Apcs.Update(apc) inc.Apcs.Update(apc)
inc.Pcds.Update(inc.Pcs.Last()) inc.Pcds.Update(inc.Pcs.Last(0))
inc.Apcds.Update(inc.Apcs.Last()) 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) inc.Values.Push(tsi)
if inc.Values.Length() > MaxNumOfEWMA { if inc.Values.Length() > MaxNumOfEWMA {
inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:]
@ -80,12 +81,12 @@ func (inc *TSI) Length() int {
return inc.Values.Length() return inc.Values.Length()
} }
func (inc *TSI) Last() float64 { func (inc *TSI) Last(i int) float64 {
return inc.Values.Last() return inc.Values.Last(i)
} }
func (inc *TSI) Index(i int) float64 { func (inc *TSI) Index(i int) float64 {
return inc.Values.Index(i) return inc.Last(i)
} }
func (inc *TSI) PushK(k types.KLine) { func (inc *TSI) PushK(k types.KLine) {

View File

@ -32,5 +32,5 @@ func Test_TSI(t *testing.T) {
} }
assert.Equal(t, tsi.Length(), 29) assert.Equal(t, tsi.Length(), 29)
Delta := 1.5e-2 Delta := 1.5e-2
assert.InDelta(t, tsi.Last(), 22.89, Delta) assert.InDelta(t, tsi.Last(0), 22.89, Delta)
} }

View File

@ -70,20 +70,20 @@ func (inc *UtBotAlert) Update(highPrice, lowPrice, closePrice float64) {
// Update ATR // Update ATR
inc.AverageTrueRange.Update(highPrice, lowPrice, closePrice) inc.AverageTrueRange.Update(highPrice, lowPrice, closePrice)
nLoss := inc.AverageTrueRange.Last() * inc.KeyValue nLoss := inc.AverageTrueRange.Last(0) * inc.KeyValue
// xATRTrailingStop // xATRTrailingStop
if inc.xATRTrailingStop.Length() == 0 { if inc.xATRTrailingStop.Length() == 0 {
// For first run // For first run
inc.xATRTrailingStop.Update(0) inc.xATRTrailingStop.Update(0)
} else if closePrice > inc.xATRTrailingStop.Index(1) && inc.previousClosePrice > inc.xATRTrailingStop.Index(1) { } else if closePrice > inc.xATRTrailingStop.Last(1) && inc.previousClosePrice > inc.xATRTrailingStop.Last(1) {
inc.xATRTrailingStop.Update(math.Max(inc.xATRTrailingStop.Index(1), closePrice-nLoss)) inc.xATRTrailingStop.Update(math.Max(inc.xATRTrailingStop.Last(1), closePrice-nLoss))
} else if closePrice < inc.xATRTrailingStop.Index(1) && inc.previousClosePrice < inc.xATRTrailingStop.Index(1) { } else if closePrice < inc.xATRTrailingStop.Last(1) && inc.previousClosePrice < inc.xATRTrailingStop.Last(1) {
inc.xATRTrailingStop.Update(math.Min(inc.xATRTrailingStop.Index(1), closePrice+nLoss)) 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) inc.xATRTrailingStop.Update(closePrice - nLoss)
} else { } else {
@ -91,19 +91,19 @@ func (inc *UtBotAlert) Update(highPrice, lowPrice, closePrice float64) {
} }
// pos // 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 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 inc.pos = types.DirectionDown
} else { } else {
inc.pos = inc.previousPos inc.pos = inc.previousPos
} }
above := 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() && inc.previousClosePrice > inc.xATRTrailingStop.Index(1) below := closePrice < inc.xATRTrailingStop.Last(0) && inc.previousClosePrice > inc.xATRTrailingStop.Last(1)
buy := closePrice > inc.xATRTrailingStop.Last() && above // buy buy := closePrice > inc.xATRTrailingStop.Last(0) && above // buy
sell := closePrice < inc.xATRTrailingStop.Last() && below // sell sell := closePrice < inc.xATRTrailingStop.Last(0) && below // sell
inc.buyValue.Push(buy) inc.buyValue.Push(buy)
inc.sellValue.Push(sell) inc.sellValue.Push(sell)

View File

@ -71,7 +71,7 @@ func Test_ATR2(t *testing.T) {
stream.EmitKLineClosed(k) stream.EmitKLineClosed(k)
} }
got := atr.Last() got := atr.Last(0)
diff := math.Trunc((got-tt.want)*100) / 100 diff := math.Trunc((got-tt.want)*100) / 100
if diff != 0 { if diff != 0 {
t.Errorf("ATR2() = %v, want %v", got, tt.want) t.Errorf("ATR2() = %v, want %v", got, tt.want)

View File

@ -30,7 +30,7 @@ func (s *EWMAStream) calculateAndPush(v float64) {
} }
func (s *EWMAStream) calculate(v float64) float64 { func (s *EWMAStream) calculate(v float64) float64 {
last := s.slice.Last() last := s.slice.Last(0)
m := s.multiplier m := s.multiplier
return (1.0-m)*last + m*v return (1.0-m)*last + m*v
} }

View File

@ -32,8 +32,8 @@ func (s *RSIStream) calculate(_ float64) float64 {
var sourceLen = s.source.Length() var sourceLen = s.source.Length()
var limit = min(s.window, sourceLen) var limit = min(s.window, sourceLen)
for i := 0; i < limit; i++ { for i := 0; i < limit; i++ {
value := s.source.Index(i) value := s.source.Last(i)
prev := s.source.Index(i + 1) prev := s.source.Last(i + 1)
change := value - prev change := value - prev
if change >= 0 { if change >= 0 {
gainSum += change gainSum += change

View File

@ -72,7 +72,7 @@ func Test_TR_and_RMA(t *testing.T) {
stream.EmitKLineClosed(k) stream.EmitKLineClosed(k)
} }
got := rma.Last() got := rma.Last(0)
diff := math.Trunc((got-tt.want)*100) / 100 diff := math.Trunc((got-tt.want)*100) / 100
if diff != 0 { if diff != 0 {
t.Errorf("RMA(TR()) = %v, want %v", got, tt.want) t.Errorf("RMA(TR()) = %v, want %v", got, tt.want)

View File

@ -58,18 +58,18 @@ func (inc *VIDYA) Update(value float64) {
change := types.Change(&inc.input) change := types.Change(&inc.input)
CMO := math.Abs(types.Sum(change, inc.Window) / types.Sum(types.Abs(change), inc.Window)) CMO := math.Abs(types.Sum(change, inc.Window) / types.Sum(types.Abs(change), inc.Window))
alpha := 2. / float64(inc.Window+1) 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 { if inc.Values.Length() > MaxNumOfEWMA {
inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:]
} }
} }
func (inc *VIDYA) Last() float64 { func (inc *VIDYA) Last(i int) float64 {
return inc.Values.Last() return inc.Values.Last(i)
} }
func (inc *VIDYA) Index(i int) float64 { func (inc *VIDYA) Index(i int) float64 {
return inc.Values.Index(i) return inc.Last(i)
} }
func (inc *VIDYA) Length() int { func (inc *VIDYA) Length() int {
@ -86,12 +86,12 @@ func (inc *VIDYA) CalculateAndUpdate(allKLines []types.KLine) {
if inc.input.Length() == 0 { if inc.input.Length() == 0 {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -10,10 +10,10 @@ import (
func Test_VIDYA(t *testing.T) { func Test_VIDYA(t *testing.T) {
vidya := &VIDYA{IntervalWindow: types.IntervalWindow{Window: 16}} vidya := &VIDYA{IntervalWindow: types.IntervalWindow{Window: 16}}
vidya.Update(1) vidya.Update(1)
assert.Equal(t, vidya.Last(), 1.) assert.Equal(t, vidya.Last(0), 1.)
vidya.Update(2) vidya.Update(2)
newV := 2./17.*2. + 1.*(1.-2./17.) newV := 2./17.*2. + 1.*(1.-2./17.)
assert.Equal(t, vidya.Last(), newV) assert.Equal(t, vidya.Last(0), newV)
vidya.Update(1) vidya.Update(1)
assert.Equal(t, vidya.Last(), vidya.Index(1)) assert.Equal(t, vidya.Last(0), vidya.Index(1))
} }

View File

@ -26,18 +26,12 @@ type Volatility struct {
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
func (inc *Volatility) Last() float64 { func (inc *Volatility) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0.0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *Volatility) Index(i int) float64 { func (inc *Volatility) Index(i int) float64 {
if len(inc.Values)-i <= 0 { return inc.Last(i)
return 0.0
}
return inc.Values[len(inc.Values)-i-1]
} }
func (inc *Volatility) Length() int { func (inc *Volatility) Length() int {

View File

@ -56,20 +56,12 @@ func (inc *VWAP) Update(price, volume float64) {
inc.Values.Push(vwap) inc.Values.Push(vwap)
} }
func (inc *VWAP) Last() float64 { func (inc *VWAP) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0.0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *VWAP) Index(i int) float64 { func (inc *VWAP) Index(i int) float64 {
length := len(inc.Values) return inc.Last(i)
if length == 0 || length-i-1 < 0 {
return 0
}
return inc.Values[length-i-1]
} }
func (inc *VWAP) Length() int { func (inc *VWAP) Length() int {
@ -91,7 +83,7 @@ func (inc *VWAP) CalculateAndUpdate(allKLines []types.KLine) {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
inc.EndTime = allKLines[len(allKLines)-1].EndTime.Time() 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 { for _, k := range klines {
vwap.Update(priceF(k), k.Volume.Float64()) vwap.Update(priceF(k), k.Volume.Float64())
} }
return vwap.Last() return vwap.Last(0)
} }

View File

@ -14,7 +14,7 @@ import (
// VWMA = SMA(pv, window) / SMA(volumes, window) // VWMA = SMA(pv, window) / SMA(volumes, window)
// //
// Volume Weighted Moving Average // 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 // 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 // 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) updateCallbacks []func(value float64)
} }
func (inc *VWMA) Last() float64 { func (inc *VWMA) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0.0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *VWMA) Index(i int) float64 { func (inc *VWMA) Index(i int) float64 {
length := len(inc.Values) return inc.Last(i)
if length == 0 || length-i-1 < 0 {
return 0
}
return inc.Values[length-i-1]
} }
func (inc *VWMA) Length() int { func (inc *VWMA) Length() int {
@ -70,8 +63,8 @@ func (inc *VWMA) Update(price, volume float64) {
inc.PriceVolumeSMA.Update(price * volume) inc.PriceVolumeSMA.Update(price * volume)
inc.VolumeSMA.Update(volume) inc.VolumeSMA.Update(volume)
pv := inc.PriceVolumeSMA.Last() pv := inc.PriceVolumeSMA.Last(0)
v := inc.VolumeSMA.Last() v := inc.VolumeSMA.Last(0)
vwma := pv / v vwma := pv / v
inc.Values.Push(vwma) inc.Values.Push(vwma)
} }
@ -104,7 +97,7 @@ func (inc *VWMA) CalculateAndUpdate(allKLines []types.KLine) {
} }
inc.EndTime = last.EndTime.Time() 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) { func (inc *VWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {

View File

@ -10,6 +10,7 @@ import (
// Refer: https://tradingview.com/script/aDymGrFx-Drift-Study-Inspired-by-Monte-Carlo-Simulations-with-BM-KL/ // Refer: https://tradingview.com/script/aDymGrFx-Drift-Study-Inspired-by-Monte-Carlo-Simulations-with-BM-KL/
// Brownian Motion's drift factor // Brownian Motion's drift factor
// could be used in Monte Carlo Simulations // could be used in Monte Carlo Simulations
//
//go:generate callbackgen -type WeightedDrift //go:generate callbackgen -type WeightedDrift
type WeightedDrift struct { type WeightedDrift struct {
types.SeriesBase types.SeriesBase
@ -54,7 +55,7 @@ func (inc *WeightedDrift) Update(value float64, weight float64) {
} }
if inc.chng.Length() >= inc.Window { if inc.chng.Length() >= inc.Window {
stdev := types.Stdev(inc.chng, 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) inc.Values.Push(drift)
} }
} }
@ -77,7 +78,7 @@ func (inc *WeightedDrift) ZeroPoint() float64 {
} else { } else {
return N2 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) { 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 { func (inc *WeightedDrift) Index(i int) float64 {
if inc.Values == nil { return inc.Last(i)
return 0
}
return inc.Values.Index(i)
} }
func (inc *WeightedDrift) Last() float64 { func (inc *WeightedDrift) Last(i int) float64 {
if inc.Values.Length() == 0 { return inc.Values.Last(i)
return 0
}
return inc.Values.Last()
} }
func (inc *WeightedDrift) Length() int { func (inc *WeightedDrift) Length() int {
@ -130,12 +125,12 @@ func (inc *WeightedDrift) CalculateAndUpdate(allKLines []types.KLine) {
if inc.chng == nil { if inc.chng == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -33,25 +33,17 @@ func (inc *WWMA) Update(value float64) {
inc.Values = inc.Values[MaxNumOfWWMATruncateSize-1:] inc.Values = inc.Values[MaxNumOfWWMATruncateSize-1:]
} }
last := inc.Last() last := inc.Last(0)
wma := last + (value-last)/float64(inc.Window) wma := last + (value-last)/float64(inc.Window)
inc.Values.Push(wma) inc.Values.Push(wma)
} }
func (inc *WWMA) Last() float64 { func (inc *WWMA) Last(i int) float64 {
if len(inc.Values) == 0 { return inc.Values.Last(i)
return 0
}
return inc.Values[len(inc.Values)-1]
} }
func (inc *WWMA) Index(i int) float64 { func (inc *WWMA) Index(i int) float64 {
if i >= len(inc.Values) { return inc.Last(i)
return 0
}
return inc.Values[len(inc.Values)-1-i]
} }
func (inc *WWMA) Length() int { func (inc *WWMA) Length() int {
@ -76,7 +68,7 @@ func (inc *WWMA) CalculateAndUpdate(allKLines []types.KLine) {
if doable { if doable {
inc.PushK(k) inc.PushK(k)
inc.LastOpenTime = k.StartTime.Time() inc.LastOpenTime = k.StartTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }
} }

View File

@ -27,17 +27,14 @@ type ZLEMA struct {
} }
func (inc *ZLEMA) Index(i int) float64 { func (inc *ZLEMA) Index(i int) float64 {
if inc.zlema == nil { return inc.Last(i)
return 0
}
return inc.zlema.Index(i)
} }
func (inc *ZLEMA) Last() float64 { func (inc *ZLEMA) Last(i int) float64 {
if inc.zlema == nil { if inc.zlema == nil {
return 0 return 0
} }
return inc.zlema.Last() return inc.zlema.Last(i)
} }
func (inc *ZLEMA) Length() int { func (inc *ZLEMA) Length() int {
@ -74,12 +71,12 @@ func (inc *ZLEMA) CalculateAndUpdate(allKLines []types.KLine) {
if inc.zlema == nil { if inc.zlema == nil {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) inc.PushK(k)
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
} }

View File

@ -46,7 +46,7 @@ func Test_ZLEMA(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
zlema := ZLEMA{IntervalWindow: types.IntervalWindow{Window: 16}} zlema := ZLEMA{IntervalWindow: types.IntervalWindow{Window: 16}}
zlema.CalculateAndUpdate(tt.kLines) zlema.CalculateAndUpdate(tt.kLines)
last := zlema.Last() last := zlema.Last(0)
assert.InDelta(t, tt.want, last, Delta) assert.InDelta(t, tt.want, last, Delta)
assert.InDelta(t, tt.next, zlema.Index(1), Delta) assert.InDelta(t, tt.next, zlema.Index(1), Delta)
assert.Equal(t, tt.all, zlema.Length()) assert.Equal(t, tt.all, zlema.Length())

View File

@ -59,9 +59,9 @@ func (d *DynamicExposureBollBand) initialize(symbol string, session *bbgo.Exchan
// getMaxExposure returns the max exposure // getMaxExposure returns the max exposure
func (d *DynamicExposureBollBand) getMaxExposure(price float64, trend types.Direction) (fixedpoint.Value, error) { func (d *DynamicExposureBollBand) getMaxExposure(price float64, trend types.Direction) (fixedpoint.Value, error) {
downBand := d.dynamicExposureBollBand.DownBand.Last() downBand := d.dynamicExposureBollBand.DownBand.Last(0)
upBand := d.dynamicExposureBollBand.UpBand.Last() upBand := d.dynamicExposureBollBand.UpBand.Last(0)
sma := d.dynamicExposureBollBand.SMA.Last() sma := d.dynamicExposureBollBand.SMA.Last(0)
log.Infof("dynamicExposureBollBand bollinger band: up %f sma %f down %f", upBand, sma, downBand) log.Infof("dynamicExposureBollBand bollinger band: up %f sma %f down %f", upBand, sma, downBand)
bandPercentage := 0.0 bandPercentage := 0.0

View File

@ -114,7 +114,7 @@ func (ds *DynamicAmpSpread) update(kline types.KLine) {
func (ds *DynamicAmpSpread) getAskSpread() (askSpread float64, err error) { func (ds *DynamicAmpSpread) getAskSpread() (askSpread float64, err error) {
if ds.AskSpreadScale != nil && ds.dynamicAskSpread.Length() >= ds.Window { 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 { if err != nil {
log.WithError(err).Errorf("can not calculate dynamicAskSpread") log.WithError(err).Errorf("can not calculate dynamicAskSpread")
return 0, err return 0, err
@ -128,7 +128,7 @@ func (ds *DynamicAmpSpread) getAskSpread() (askSpread float64, err error) {
func (ds *DynamicAmpSpread) getBidSpread() (bidSpread float64, err error) { func (ds *DynamicAmpSpread) getBidSpread() (bidSpread float64, err error) {
if ds.BidSpreadScale != nil && ds.dynamicBidSpread.Length() >= ds.Window { 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 { if err != nil {
log.WithError(err).Errorf("can not calculate dynamicBidSpread") log.WithError(err).Errorf("can not calculate dynamicBidSpread")
return 0, err return 0, err
@ -224,12 +224,12 @@ func (ds *DynamicSpreadBollWidthRatio) getWeightedBBWidthRatio(positiveSigmoid b
// - To ask spread, the higher neutral band get greater ratio // - To ask spread, the higher neutral band get greater ratio
// - To bid spread, the lower neutral band get greater ratio // - To bid spread, the lower neutral band get greater ratio
defaultMid := ds.defaultBoll.SMA.Last() defaultMid := ds.defaultBoll.SMA.Last(0)
defaultUpper := ds.defaultBoll.UpBand.Last() defaultUpper := ds.defaultBoll.UpBand.Last(0)
defaultLower := ds.defaultBoll.DownBand.Last() defaultLower := ds.defaultBoll.DownBand.Last(0)
defaultWidth := defaultUpper - defaultLower defaultWidth := defaultUpper - defaultLower
neutralUpper := ds.neutralBoll.UpBand.Last() neutralUpper := ds.neutralBoll.UpBand.Last(0)
neutralLower := ds.neutralBoll.DownBand.Last() neutralLower := ds.neutralBoll.DownBand.Last(0)
factor := defaultWidth / ds.Sensitivity factor := defaultWidth / ds.Sensitivity
var weightedUpper, weightedLower, weightedDivUpper, weightedDivLower float64 var weightedUpper, weightedLower, weightedDivUpper, weightedDivLower float64
if positiveSigmoid { if positiveSigmoid {

View File

@ -92,7 +92,7 @@ func (s *PerTrade) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
// min-max scaling // min-max scaling
ofsMax := orderFlowSize.Tail(100).Max() ofsMax := orderFlowSize.Tail(100).Max()
ofsMin := orderFlowSize.Tail(100).Min() 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 // preserves temporal dependency via polar encoded angles
orderFlowSizeMinMax.Push(ofsMinMax) orderFlowSizeMinMax.Push(ofsMinMax)
} }
@ -102,7 +102,7 @@ func (s *PerTrade) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
// min-max scaling // min-max scaling
ofnMax := orderFlowNumber.Tail(100).Max() ofnMax := orderFlowNumber.Tail(100).Max()
ofnMin := orderFlowNumber.Tail(100).Min() 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 // preserves temporal dependency via polar encoded angles
orderFlowNumberMinMax.Push(ofnMinMax) 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 { func outlier(fs floats.Slice, multiplier float64) int {
stddev := stat.StdDev(fs, nil) stddev := stat.StdDev(fs, nil)
if fs.Last() > fs.Mean()+multiplier*stddev { if fs.Last(0) > fs.Mean()+multiplier*stddev {
return 1 return 1
} else if fs.Last() < fs.Mean()-multiplier*stddev { } else if fs.Last(0) < fs.Mean()-multiplier*stddev {
return -1 return -1
} }
return 0 return 0

View File

@ -135,7 +135,7 @@ func (s *Strategy) generateGridBuyOrders(session *bbgo.ExchangeSession) ([]types
ema99 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 99}) ema99 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 99})
ema25 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 25}) ema25 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 25})
ema7 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 7}) 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") log.Infof("all ema lines trend up, skip buy")
return nil, nil 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}) ema99 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 99})
ema25 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 25}) ema25 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 25})
ema7 := s.StandardIndicatorSet.EWMA(types.IntervalWindow{Interval: s.Interval, Window: 7}) 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") log.Infof("all ema lines trend down, skip sell")
return nil, nil return nil, nil
} }

View File

@ -126,7 +126,7 @@ func (ds *DynamicSpreadAmpSettings) update(kline types.KLine) {
func (ds *DynamicSpreadAmpSettings) getAskSpread() (askSpread float64, err error) { func (ds *DynamicSpreadAmpSettings) getAskSpread() (askSpread float64, err error) {
if ds.AskSpreadScale != nil && ds.dynamicAskSpread.Length() >= ds.Window { 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 { if err != nil {
log.WithError(err).Errorf("can not calculate dynamicAskSpread") log.WithError(err).Errorf("can not calculate dynamicAskSpread")
return 0, err return 0, err
@ -140,7 +140,7 @@ func (ds *DynamicSpreadAmpSettings) getAskSpread() (askSpread float64, err error
func (ds *DynamicSpreadAmpSettings) getBidSpread() (bidSpread float64, err error) { func (ds *DynamicSpreadAmpSettings) getBidSpread() (bidSpread float64, err error) {
if ds.BidSpreadScale != nil && ds.dynamicBidSpread.Length() >= ds.Window { 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 { if err != nil {
log.WithError(err).Errorf("can not calculate dynamicBidSpread") log.WithError(err).Errorf("can not calculate dynamicBidSpread")
return 0, err return 0, err
@ -224,12 +224,12 @@ func (ds *DynamicSpreadBollWidthRatioSettings) getWeightedBBWidthRatio(positiveS
// - To ask spread, the higher neutral band get greater ratio // - To ask spread, the higher neutral band get greater ratio
// - To bid spread, the lower neutral band get greater ratio // - To bid spread, the lower neutral band get greater ratio
defaultMid := ds.defaultBoll.SMA.Last() defaultMid := ds.defaultBoll.SMA.Last(0)
defaultUpper := ds.defaultBoll.UpBand.Last() defaultUpper := ds.defaultBoll.UpBand.Last(0)
defaultLower := ds.defaultBoll.DownBand.Last() defaultLower := ds.defaultBoll.DownBand.Last(0)
defaultWidth := defaultUpper - defaultLower defaultWidth := defaultUpper - defaultLower
neutralUpper := ds.neutralBoll.UpBand.Last() neutralUpper := ds.neutralBoll.UpBand.Last(0)
neutralLower := ds.neutralBoll.DownBand.Last() neutralLower := ds.neutralBoll.DownBand.Last(0)
factor := defaultWidth / ds.Sensitivity factor := defaultWidth / ds.Sensitivity
var weightedUpper, weightedLower, weightedDivUpper, weightedDivLower float64 var weightedUpper, weightedLower, weightedDivUpper, weightedDivLower float64
if positiveSigmoid { if positiveSigmoid {

View File

@ -278,9 +278,9 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
baseBalance, hasBaseBalance := balances[s.Market.BaseCurrency] baseBalance, hasBaseBalance := balances[s.Market.BaseCurrency]
quoteBalance, hasQuoteBalance := balances[s.Market.QuoteCurrency] quoteBalance, hasQuoteBalance := balances[s.Market.QuoteCurrency]
downBand := s.defaultBoll.DownBand.Last() downBand := s.defaultBoll.DownBand.Last(0)
upBand := s.defaultBoll.UpBand.Last() upBand := s.defaultBoll.UpBand.Last(0)
sma := s.defaultBoll.SMA.Last() sma := s.defaultBoll.SMA.Last(0)
log.Infof("%s bollinger band: up %f sma %f down %f", s.Symbol, upBand, sma, downBand) log.Infof("%s bollinger band: up %f sma %f down %f", s.Symbol, upBand, sma, downBand)
bandPercentage := calculateBandPercentage(upBand, downBand, sma, midPrice.Float64()) bandPercentage := calculateBandPercentage(upBand, downBand, sma, midPrice.Float64())
@ -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 // WHEN: price breaks the upper band (price > window 2) == strongUpTrend
// THEN: we apply strongUpTrend skew // THEN: we apply strongUpTrend skew
if s.TradeInBand { 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") log.Infof("tradeInBand is set, skip placing orders when the price is outside of the band")
return return
} }
@ -410,7 +410,7 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
canSell = false canSell = false
} }
if s.BuyBelowNeutralSMA && midPrice.Float64() > s.neutralBoll.SMA.Last() { if s.BuyBelowNeutralSMA && midPrice.Float64() > s.neutralBoll.SMA.Last(0) {
canBuy = false canBuy = false
} }

View File

@ -12,7 +12,7 @@ const (
) )
func detectPriceTrend(inc *indicator.BOLL, price float64) PriceTrend { 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 return NeutralTrend
} }

View File

@ -17,19 +17,19 @@ func (s *DriftMA) Update(value, weight float64) {
if s.ma1.Length() == 0 { if s.ma1.Length() == 0 {
return return
} }
s.drift.Update(s.ma1.Last(), weight) s.drift.Update(s.ma1.Last(0), weight)
if s.drift.Length() == 0 { if s.drift.Length() == 0 {
return return
} }
s.ma2.Update(s.drift.Last()) s.ma2.Update(s.drift.Last(0))
} }
func (s *DriftMA) Last() float64 { func (s *DriftMA) Last(int) float64 {
return s.ma2.Last() return s.ma2.Last(0)
} }
func (s *DriftMA) Index(i int) float64 { func (s *DriftMA) Index(i int) float64 {
return s.ma2.Index(i) return s.ma2.Last(i)
} }
func (s *DriftMA) Length() int { func (s *DriftMA) Length() int {

View File

@ -9,7 +9,7 @@ func (s *Strategy) CheckStopLoss() bool {
} }
} }
if s.UseAtr { if s.UseAtr {
atr := s.atr.Last() atr := s.atr.Last(0)
if s.sellPrice > 0 && s.sellPrice+atr <= s.highestPrice || if s.sellPrice > 0 && s.sellPrice+atr <= s.highestPrice ||
s.buyPrice > 0 && s.buyPrice-atr >= s.lowestPrice { s.buyPrice > 0 && s.buyPrice-atr >= s.lowestPrice {
return true return true

View File

@ -261,8 +261,8 @@ func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error {
high := kline.High.Float64() high := kline.High.Float64()
low := kline.Low.Float64() low := kline.Low.Float64()
s.ma.Update(source) s.ma.Update(source)
s.stdevHigh.Update(high - s.ma.Last()) s.stdevHigh.Update(high - s.ma.Last(0))
s.stdevLow.Update(s.ma.Last() - low) s.stdevLow.Update(s.ma.Last(0) - low)
s.drift.Update(source, kline.Volume.Abs().Float64()) s.drift.Update(source, kline.Volume.Abs().Float64())
s.trendLine.Update(source) s.trendLine.Update(source)
s.atr.PushK(kline) s.atr.PushK(kline)
@ -485,7 +485,7 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine, count
return return
} }
// for doing the trailing stoploss during backtesting // for doing the trailing stoploss during backtesting
atr := s.atr.Last() atr := s.atr.Last(0)
price := s.getLastPrice() price := s.getLastPrice()
pricef := price.Float64() 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.drift.Update(sourcef, kline.Volume.Abs().Float64())
s.atr.PushK(kline) s.atr.PushK(kline)
atr := s.atr.Last() atr := s.atr.Last(0)
price := kline.Close // s.getLastPrice() price := kline.Close // s.getLastPrice()
pricef := price.Float64() pricef := price.Float64()
@ -563,7 +563,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter
return 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() s.positionLock.Lock()
if s.lowestPrice > 0 && lowf < s.lowestPrice { 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) 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) 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 shortCondition && longCondition {
if s.priceLines.Index(1) > s.priceLines.Last() { if s.priceLines.Index(1) > s.priceLines.Last(0) {
longCondition = false longCondition = false
} else { } else {
shortCondition = false shortCondition = false
@ -625,7 +625,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter
} }
if longCondition { 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 { if source.Compare(price) > 0 {
source = price source = price
} }
@ -654,7 +654,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter
return 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) o, err := s.SubmitOrder(ctx, *submitOrder)
if err != nil { if err != nil {
@ -675,12 +675,12 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter
return return
} }
if shortCondition { 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 { if source.Compare(price) < 0 {
source = price 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 := s.OpenPositionOptions
opt.Short = true opt.Short = true

View File

@ -11,8 +11,8 @@ func (s *ElliottWave) Index(i int) float64 {
return s.maQuick.Index(i)/s.maSlow.Index(i) - 1.0 return s.maQuick.Index(i)/s.maSlow.Index(i) - 1.0
} }
func (s *ElliottWave) Last() float64 { func (s *ElliottWave) Last(int) float64 {
return s.maQuick.Last()/s.maSlow.Last() - 1.0 return s.maQuick.Last(0)/s.maSlow.Last(0) - 1.0
} }
func (s *ElliottWave) Length() int { func (s *ElliottWave) Length() int {

View File

@ -199,12 +199,12 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef float64) int {
if s.counter-s.orderPendingCounter[order.OrderID] >= s.PendingMinInterval { if s.counter-s.orderPendingCounter[order.OrderID] >= s.PendingMinInterval {
toCancel = true toCancel = true
} else if order.Side == types.SideTypeBuy { } 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 toCancel = true
} }
} else if order.Side == types.SideTypeSell { } else if order.Side == types.SideTypeSell {
// 75% of the probability // 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 toCancel = true
} }
} else { } else {
@ -425,7 +425,7 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine) {
stoploss := s.Stoploss.Float64() stoploss := s.Stoploss.Float64()
price := s.getLastPrice() price := s.getLastPrice()
pricef := price.Float64() pricef := price.Float64()
atr := s.atr.Last() atr := s.atr.Last(0)
numPending := s.smartCancel(ctx, pricef) numPending := s.smartCancel(ctx, pricef)
if numPending > 0 { if numPending > 0 {
@ -476,7 +476,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.smartCancel(ctx, pricef) s.smartCancel(ctx, pricef)
atr := s.atr.Last() atr := s.atr.Last(0)
ewo := types.Array(s.ewo, 4) ewo := types.Array(s.ewo, 4)
if len(ewo) < 4 { if len(ewo) < 4 {
return return

View File

@ -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) { func (s *Strategy) place(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession, indicator types.Float64Indicator, closePrice fixedpoint.Value) {
closePriceF := closePrice.Float64() closePriceF := closePrice.Float64()
movingAveragePriceF := indicator.Last() movingAveragePriceF := indicator.Last(0)
// skip it if it's near zero because it's not loaded yet // skip it if it's near zero because it's not loaded yet
if movingAveragePriceF < 0.0001 { if movingAveragePriceF < 0.0001 {

View File

@ -27,11 +27,11 @@ func NewHeikinAshi(size int) *HeikinAshi {
func (s *HeikinAshi) Print() string { func (s *HeikinAshi) Print() string {
return fmt.Sprintf("Heikin c: %.3f, o: %.3f, h: %.3f, l: %.3f, v: %.3f", return fmt.Sprintf("Heikin c: %.3f, o: %.3f, h: %.3f, l: %.3f, v: %.3f",
s.Close.Last(), s.Close.Last(0),
s.Open.Last(), s.Open.Last(0),
s.High.Last(), s.High.Last(0),
s.Low.Last(), s.Low.Last(0),
s.Volume.Last()) s.Volume.Last(0))
} }
func (inc *HeikinAshi) Update(kline types.KLine) { func (inc *HeikinAshi) Update(kline types.KLine) {
@ -40,7 +40,7 @@ func (inc *HeikinAshi) Update(kline types.KLine) {
high := kline.High.Float64() high := kline.High.Float64()
low := kline.Low.Float64() low := kline.Low.Float64()
newClose := (open + high + low + cloze) / 4. 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.Close.Update(newClose)
inc.Open.Update(newOpen) inc.Open.Update(newOpen)
inc.High.Update(math.Max(math.Max(high, newOpen), newClose)) inc.High.Update(math.Max(math.Max(high, newOpen), newClose))

View File

@ -139,7 +139,7 @@ func NewCCISTOCH(i types.Interval, filterHigh, filterLow float64) *CCISTOCH {
func (inc *CCISTOCH) Update(cloze float64) { func (inc *CCISTOCH) Update(cloze float64) {
inc.cci.Update(cloze) 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()) inc.ma.Update(inc.stoch.LastD())
} }
@ -180,19 +180,19 @@ type VWEMA struct {
V types.UpdatableSeries V types.UpdatableSeries
} }
func (inc *VWEMA) Last() float64 { func (inc *VWEMA) Last(int) float64 {
return inc.PV.Last() / inc.V.Last() return inc.PV.Last(0) / inc.V.Last(0)
} }
func (inc *VWEMA) Index(i int) float64 { func (inc *VWEMA) Index(i int) float64 {
if i >= inc.PV.Length() { if i >= inc.PV.Length() {
return 0 return 0
} }
vi := inc.V.Index(i) vi := inc.V.Last(i)
if vi == 0 { if vi == 0 {
return 0 return 0
} }
return inc.PV.Index(i) / vi return inc.PV.Last(i) / vi
} }
func (inc *VWEMA) Length() int { func (inc *VWEMA) Length() int {
@ -262,11 +262,11 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
if s.heikinAshi.Close.Length() == 0 { if s.heikinAshi.Close.Length() == 0 {
for _, kline := range window { for _, kline := range window {
s.heikinAshi.Update(kline) s.heikinAshi.Update(kline)
s.ccis.Update(getSource(window).Last()) s.ccis.Update(getSource(window).Last(0))
} }
} else { } else {
s.heikinAshi.Update(window[len(window)-1]) s.heikinAshi.Update(window[len(window)-1])
s.ccis.Update(getSource(window).Last()) s.ccis.Update(getSource(window).Last(0))
} }
}) })
if s.UseEma { if s.UseEma {
@ -283,7 +283,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
ema34.Update(cloze) ema34.Update(cloze)
} }
} else { } else {
cloze := getSource(window).Last() cloze := getSource(window).Last(0)
ema5.Update(cloze) ema5.Update(cloze)
ema34.Update(cloze) ema34.Update(cloze)
} }
@ -306,7 +306,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
sma34.Update(cloze) sma34.Update(cloze)
} }
} else { } else {
cloze := getSource(window).Last() cloze := getSource(window).Last(0)
sma5.Update(cloze) sma5.Update(cloze)
sma34.Update(cloze) sma34.Update(cloze)
} }
@ -330,14 +330,14 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
vols := getVol(window) vols := getVol(window)
if evwma5.PV.Length() == 0 { if evwma5.PV.Length() == 0 {
for i := clozes.Length() - 1; i >= 0; i-- { for i := clozes.Length() - 1; i >= 0; i-- {
price := clozes.Index(i) price := clozes.Last(i)
vol := vols.Index(i) vol := vols.Last(i)
evwma5.UpdateVal(price, vol) evwma5.UpdateVal(price, vol)
evwma34.UpdateVal(price, vol) evwma34.UpdateVal(price, vol)
} }
} else { } else {
price := clozes.Last() price := clozes.Last(0)
vol := vols.Last() vol := vols.Last(0)
evwma5.UpdateVal(price, vol) evwma5.UpdateVal(price, vol)
evwma34.UpdateVal(price, vol) evwma34.UpdateVal(price, vol)
} }
@ -363,7 +363,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
sig.Update(ewoValue) sig.Update(ewoValue)
} }
} else { } else {
sig.Update(s.ewo.Last()) sig.Update(s.ewo.Last(0))
} }
}) })
s.ewoSignal = sig s.ewoSignal = sig
@ -381,7 +381,7 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
sig.Update(ewoValue) sig.Update(ewoValue)
} }
} else { } else {
sig.Update(s.ewo.Last()) sig.Update(s.ewo.Last(0))
} }
}) })
s.ewoSignal = sig s.ewoSignal = sig
@ -398,13 +398,13 @@ func (s *Strategy) SetupIndicators(store *bbgo.MarketDataStore) {
// lazy init // lazy init
ewoVals := s.ewo.Reverse() ewoVals := s.ewo.Reverse()
for i, ewoValue := range ewoVals { for i, ewoValue := range ewoVals {
vol := window.Volume().Index(i) vol := window.Volume().Last(i)
sig.PV.Update(ewoValue * vol) sig.PV.Update(ewoValue * vol)
sig.V.Update(vol) sig.V.Update(vol)
} }
} else { } else {
vol := window.Volume().Last() vol := window.Volume().Last(0)
sig.PV.Update(s.ewo.Last() * vol) sig.PV.Update(s.ewo.Last(0) * vol)
sig.V.Update(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) // - TP by (lastprice < peak price - atr) || (lastprice > bottom price + atr)
// - SL by s.StopLoss (Abs(price_diff / price) > s.StopLoss) // - SL by s.StopLoss (Abs(price_diff / price) > s.StopLoss)
// - entry condition on ewo(Elliott wave oscillator) Crosses ewoSignal(ma on ewo, signalWindow) // - 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) // - 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) // - sell signal on (crossunder on previous K bar and no crossunder on latest K bar)
//
// - and filtered by the following rules: // - and filtered by the following rules:
// * buy: buy signal ON, kline Close > Open, Close > ma5, Close > ma34, CCI Stochastic Buy 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 // - sell: sell signal ON, kline Close < Open, Close < ma5, Close < ma34, CCI Stochastic Sell signal
//
// - or entry when ma34 +- atr * 3 gets touched // - or entry when ma34 +- atr * 3 gets touched
// - entry price: latestPrice +- atr / 2 (short,long), close at market price // - 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) // 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) s.SetupIndicators(store)
// local peak of ewo // local peak of ewo
shortSig := 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() > s.ewo.Index(1) && s.ewo.Index(1) < s.ewo.Index(2) longSig := s.ewo.Last(0) > s.ewo.Last(1) && s.ewo.Last(1) < s.ewo.Last(2)
sellOrderTPSL := func(price fixedpoint.Value) { sellOrderTPSL := func(price fixedpoint.Value) {
lastPrice := s.GetLastPrice() lastPrice := s.GetLastPrice()
@ -798,8 +800,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
balances := session.GetAccount().Balances() balances := session.GetAccount().Balances()
quoteBalance := balances[s.Market.QuoteCurrency].Available quoteBalance := balances[s.Market.QuoteCurrency].Available
atr := fixedpoint.NewFromFloat(s.atr.Last()) atr := fixedpoint.NewFromFloat(s.atr.Last(0))
atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2) atrx2 := fixedpoint.NewFromFloat(s.atr.Last(0) * 2)
buyall := false buyall := false
if s.bottomPrice.IsZero() || s.bottomPrice.Compare(price) > 0 { if s.bottomPrice.IsZero() || s.bottomPrice.Compare(price) > 0 {
s.bottomPrice = price s.bottomPrice = price
@ -809,7 +811,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
spBack := s.sellPrice spBack := s.sellPrice
reason := -1 reason := -1
if quoteBalance.Div(lastPrice).Compare(s.Market.MinQuantity) >= 0 && quoteBalance.Compare(s.Market.MinNotional) >= 0 { 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 // TP
if lastPrice.Compare(s.sellPrice) < 0 && (longSig || if lastPrice.Compare(s.sellPrice) < 0 && (longSig ||
(!atrx2.IsZero() && base.Sub(atrx2).Compare(lastPrice) >= 0)) { (!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() balances := session.GetAccount().Balances()
baseBalance := balances[s.Market.BaseCurrency].Available baseBalance := balances[s.Market.BaseCurrency].Available
atr := fixedpoint.NewFromFloat(s.atr.Last()) atr := fixedpoint.NewFromFloat(s.atr.Last(0))
atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2) atrx2 := fixedpoint.NewFromFloat(s.atr.Last(0) * 2)
sellall := false sellall := false
if s.peakPrice.IsZero() || s.peakPrice.Compare(price) < 0 { if s.peakPrice.IsZero() || s.peakPrice.Compare(price) < 0 {
s.peakPrice = price s.peakPrice = price
@ -915,7 +917,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
reason := -1 reason := -1
if baseBalance.Compare(s.Market.MinQuantity) >= 0 && baseBalance.Mul(lastPrice).Compare(s.Market.MinNotional) >= 0 { if baseBalance.Compare(s.Market.MinQuantity) >= 0 && baseBalance.Mul(lastPrice).Compare(s.Market.MinNotional) >= 0 {
// TP // TP
base := fixedpoint.NewFromFloat(s.ma34.Last()) base := fixedpoint.NewFromFloat(s.ma34.Last(0))
if lastPrice.Compare(s.buyPrice) > 0 && (shortSig || if lastPrice.Compare(s.buyPrice) > 0 && (shortSig ||
(!atrx2.IsZero() && base.Add(atrx2).Compare(lastPrice) <= 0)) { (!atrx2.IsZero() && base.Add(atrx2).Compare(lastPrice) <= 0)) {
sellall = true sellall = true
@ -1089,10 +1091,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
balances := session.GetAccount().Balances() balances := session.GetAccount().Balances()
baseBalance := balances[s.Market.BaseCurrency].Total() baseBalance := balances[s.Market.BaseCurrency].Total()
quoteBalance := balances[s.Market.QuoteCurrency].Total() quoteBalance := balances[s.Market.QuoteCurrency].Total()
atr := fixedpoint.NewFromFloat(s.atr.Last()) atr := fixedpoint.NewFromFloat(s.atr.Last(0))
if !s.Environment.IsBackTesting() { 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", 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 { 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 priceChangeRate := (priceHighest - priceLowest) / priceHighest / 14
ewoHighest := types.Highest(s.ewoHistogram, 233) 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) longSignal := types.CrossOver(s.ewo, s.ewoSignal)
shortSignal := types.CrossUnder(s.ewo, s.ewoSignal) shortSignal := types.CrossUnder(s.ewo, s.ewoSignal)
base := s.ma34.Last() base := s.ma34.Last(0)
sellLine := base + s.atr.Last()*3 sellLine := base + s.atr.Last(0)*3
buyLine := base - s.atr.Last()*3 buyLine := base - s.atr.Last(0)*3
clozes := getClose(window) clozes := getClose(window)
opens := getOpen(window) opens := getOpen(window)
// get trend flags // get trend flags
bull := clozes.Last() > opens.Last() bull := clozes.Last(0) > opens.Last(0)
breakThrough := clozes.Last() > s.ma5.Last() && clozes.Last() > s.ma34.Last() breakThrough := clozes.Last(0) > s.ma5.Last(0) && clozes.Last(0) > s.ma34.Last(0)
breakDown := clozes.Last() < s.ma5.Last() && clozes.Last() < s.ma34.Last() 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 // kline breakthrough ma5, ma34 trend up, and cci Stochastic bull
IsBull := bull && breakThrough && s.ccis.BuySignal() && s.ewoChangeRate < s.EwoChangeFilterHigh && s.ewoChangeRate > s.EwoChangeFilterLow 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 // backup, since the s.sellPrice will be cleared when doing ClosePosition
sellPrice := s.sellPrice 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 // calculate report
if closeOrder, _ := s.PlaceBuyOrder(ctx, price); closeOrder != nil { 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 // backup, since the s.buyPrice will be cleared when doing ClosePosition
buyPrice := s.buyPrice 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 // calculate report
if closeOrder, _ := s.PlaceSellOrder(ctx, price); closeOrder != nil { if closeOrder, _ := s.PlaceSellOrder(ctx, price); closeOrder != nil {

View File

@ -34,14 +34,14 @@ func (inc *MOM) Index(i int) float64 {
if inc.Values == nil { if inc.Values == nil {
return 0 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 { if inc.Values.Length() == 0 {
return 0 return 0
} }
return inc.Values.Last() return inc.Values.Last(0)
} }
func (inc *MOM) Length() int { func (inc *MOM) Length() int {
@ -51,7 +51,7 @@ func (inc *MOM) Length() int {
return inc.Values.Length() return inc.Values.Length()
} }
//var _ types.SeriesExtend = &MOM{} // var _ types.SeriesExtend = &MOM{}
func (inc *MOM) Update(open, close float64) { func (inc *MOM) Update(open, close float64) {
if inc.SeriesBase.Series == nil { if inc.SeriesBase.Series == nil {
@ -62,7 +62,7 @@ func (inc *MOM) Update(open, close float64) {
inc.opens.Update(open) inc.opens.Update(open)
inc.closes.Update(close) inc.closes.Update(close)
if inc.opens.Length() >= inc.Window && inc.closes.Length() >= inc.Window { 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) inc.Values.Push(gap)
} }
} }
@ -72,11 +72,11 @@ func (inc *MOM) CalculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) 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.Update(k.Open.Float64(), k.Close.Float64())
inc.EndTime = k.EndTime.Time() 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) { func calculateMomentum(klines []types.KLine, window int, valA KLineValueMapper, valB KLineValueMapper) (float64, error) {

View File

@ -35,12 +35,12 @@ func (inc *PMR) Update(price float64) {
} }
inc.SMA.Update(price) inc.SMA.Update(price)
if inc.SMA.Length() >= inc.Window { if inc.SMA.Length() >= inc.Window {
reversion := inc.SMA.Last() / price reversion := inc.SMA.Last(0) / price
inc.Values.Push(reversion) inc.Values.Push(reversion)
} }
} }
func (inc *PMR) Last() float64 { func (inc *PMR) Last(int) float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0 return 0
} }
@ -65,11 +65,11 @@ func (inc *PMR) CalculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) 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.Update(indicator.KLineClosePriceMapper(k))
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
func CalculateKLinesPMR(allKLines []types.KLine, window int) float64 { func CalculateKLinesPMR(allKLines []types.KLine, window int) float64 {

View File

@ -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 { if len(inc.Values) == 0 {
return 0 return 0
} }
@ -72,11 +72,11 @@ func (inc *PVD) CalculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) 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.Update(indicator.KLineClosePriceMapper(k), indicator.KLineVolumeMapper(k))
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
func CalculateKLinesPVD(allKLines []types.KLine, window int) float64 { func CalculateKLinesPVD(allKLines []types.KLine, window int) float64 {

View File

@ -30,12 +30,12 @@ func (inc *RR) Update(price float64) {
inc.prices = types.NewQueue(inc.Window) inc.prices = types.NewQueue(inc.Window)
} }
inc.prices.Update(price) 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) inc.Values.Push(irr)
} }
func (inc *RR) Last() float64 { func (inc *RR) Last(int) float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0 return 0
} }
@ -60,11 +60,11 @@ func (inc *RR) CalculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) 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.Update(indicator.KLineClosePriceMapper(k))
inc.EndTime = k.EndTime.Time() inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
func (inc *RR) LoadK(allKLines []types.KLine) { func (inc *RR) LoadK(allKLines []types.KLine) {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} }
//func calculateReturn(klines []types.KLine, window int, val KLineValueMapper) (float64, error) { //func calculateReturn(klines []types.KLine, window int, val KLineValueMapper) (float64, error) {

View File

@ -33,14 +33,14 @@ func (inc *VMOM) Index(i int) float64 {
if inc.Values == nil { if inc.Values == nil {
return 0 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 { if inc.Values.Length() == 0 {
return 0 return 0
} }
return inc.Values.Last() return inc.Values.Last(0)
} }
func (inc *VMOM) Length() int { func (inc *VMOM) Length() int {
@ -59,7 +59,7 @@ func (inc *VMOM) Update(volume float64) {
} }
inc.volumes.Update(volume) inc.volumes.Update(volume)
if inc.volumes.Length() >= inc.Window { 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) inc.Values.Push(v)
} }
} }
@ -69,11 +69,11 @@ func (inc *VMOM) CalculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines { for _, k := range allKLines {
inc.PushK(k) inc.PushK(k)
} }
inc.EmitUpdate(inc.Last()) inc.EmitUpdate(inc.Last(0))
} else { } else {
k := allKLines[len(allKLines)-1] k := allKLines[len(allKLines)-1]
inc.PushK(k) 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.Update(k.Volume.Float64())
inc.EndTime = k.EndTime.Time() 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) { 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 += valV(p)
} }
vma /= float64(window) 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 return momentum, nil
} }

View File

@ -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) // use the last value from indicators, or the SeriesExtends' predict function. (e.g., look back: 5)
input := []float64{ input := []float64{
s.divergence.Last(), s.divergence.Last(0),
s.reversion.Last(), s.reversion.Last(0),
s.drift.Last(), s.drift.Last(0),
s.momentum.Last(), s.momentum.Last(0),
s.volume.Last(), s.volume.Last(0),
} }
pred := model.Predict(input) pred := model.Predict(input)
predLst.Update(pred) predLst.Update(pred)

View File

@ -213,7 +213,7 @@ func (s *Strategy) generateSubmitOrders(ctx context.Context) ([]types.SubmitOrde
log.Infof("mid price: %+v", midPrice) log.Infof("mid price: %+v", midPrice)
if s.ATRMultiplier.Float64() > 0 { if s.ATRMultiplier.Float64() > 0 {
atr := fixedpoint.NewFromFloat(s.atr.Last()) atr := fixedpoint.NewFromFloat(s.atr.Last(0))
log.Infof("atr: %s", atr.String()) log.Infof("atr: %s", atr.String())
s.HalfSpreadRatio = s.ATRMultiplier.Mul(atr).Div(midPrice) s.HalfSpreadRatio = s.ATRMultiplier.Mul(atr).Div(midPrice)
log.Infof("half spread ratio: %s", s.HalfSpreadRatio.String()) log.Infof("half spread ratio: %s", s.HalfSpreadRatio.String())

View File

@ -75,7 +75,7 @@ func (s *Strategy) updateBidOrders(orderExecutor bbgo.OrderExecutor, session *bb
return 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 var submitOrders []types.SubmitOrder
for i := 0; i < s.GridNum; i++ { for i := 0; i < s.GridNum; i++ {

View File

@ -21,7 +21,7 @@ type A18 struct {
UpdateCallbacks []func(val float64) UpdateCallbacks []func(val float64)
} }
func (inc *A18) Last() float64 { func (inc *A18) Last(int) float64 {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
return 0.0 return 0.0
} }
@ -84,8 +84,8 @@ func calculateA18(klines []types.KLine, valClose KLineValueMapper) (float64, err
closes.Push(valClose(k)) closes.Push(valClose(k))
} }
delay5 := closes.Index(4) delay5 := closes.Last(4)
curr := closes.Index(0) curr := closes.Last(0)
alpha := curr / delay5 alpha := curr / delay5
return alpha, nil return alpha, nil

Some files were not shown because too many files have changed in this diff Show More