package indicator import ( "time" "github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/types" ) const MaxNumOfRMA = 1000 const MaxNumOfRMATruncateSize = 500 // Running Moving Average // Refer: https://github.com/twopirllc/pandas-ta/blob/main/pandas_ta/overlap/rma.py#L5 // Refer: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html#pandas-dataframe-ewm // // The Running Moving Average (RMA) 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 weighting factors // determined by the length of the moving average. The resulting average is then plotted on the price chart as a line, which can be used to // make predictions about future price movements. The RMA is typically more responsive to changes in the underlying data than a simple // moving average, but may be less reliable in trending markets. It is often used in conjunction with other technical analysis indicators // to confirm signals or provide additional information about the security's price. //go:generate callbackgen -type RMA type RMA struct { types.SeriesBase types.IntervalWindow Values floats.Slice EndTime time.Time counter int Adjust bool tmp float64 sum float64 updateCallbacks []func(value float64) } func (inc *RMA) Clone() types.UpdatableSeriesExtend { out := &RMA{ IntervalWindow: inc.IntervalWindow, Values: inc.Values[:], counter: inc.counter, Adjust: inc.Adjust, tmp: inc.tmp, sum: inc.sum, EndTime: inc.EndTime, } out.SeriesBase.Series = out return out } func (inc *RMA) Update(x float64) { lambda := 1 / float64(inc.Window) if inc.counter == 0 { inc.SeriesBase.Series = inc inc.sum = 1 inc.tmp = x } else { if inc.Adjust { inc.sum = inc.sum*(1-lambda) + 1 inc.tmp = inc.tmp + (x-inc.tmp)/inc.sum } else { inc.tmp = inc.tmp*(1-lambda) + x*lambda } } inc.counter++ inc.Values.Push(inc.tmp) if len(inc.Values) > MaxNumOfRMA { inc.Values = inc.Values[MaxNumOfRMATruncateSize-1:] } } func (inc *RMA) Last(i int) float64 { return inc.Values.Last(i) } func (inc *RMA) Index(i int) float64 { return inc.Last(i) } func (inc *RMA) Length() int { return len(inc.Values) } var _ types.SeriesExtend = &RMA{} func (inc *RMA) PushK(k types.KLine) { inc.Update(k.Close.Float64()) inc.EndTime = k.EndTime.Time() } func (inc *RMA) CalculateAndUpdate(kLines []types.KLine) { last := kLines[len(kLines)-1] if len(inc.Values) == 0 { for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } inc.PushK(k) } } else { inc.PushK(last) } inc.EmitUpdate(inc.Last(0)) } func (inc *RMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { if inc.Interval != interval { return } inc.CalculateAndUpdate(window) } func (inc *RMA) Bind(updater KLineWindowUpdater) { updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) }