package factorzoo import ( "time" "gonum.org/v1/gonum/stat" "git.qtrade.icu/lychiyu/bbgo/pkg/datatype/floats" "git.qtrade.icu/lychiyu/bbgo/pkg/indicator" "git.qtrade.icu/lychiyu/bbgo/pkg/types" ) // price mean reversion // assume that the quotient of SMA over close price will dynamically revert into one. // so this fraction value is our alpha, PMR //go:generate callbackgen -type PMR type PMR struct { types.IntervalWindow types.SeriesBase Values floats.Slice SMA *indicator.SMA EndTime time.Time updateCallbacks []func(value float64) } var _ types.SeriesExtend = &PMR{} func (inc *PMR) Update(price float64) { if inc.SeriesBase.Series == nil { inc.SeriesBase.Series = inc inc.SMA = &indicator.SMA{IntervalWindow: inc.IntervalWindow} } inc.SMA.Update(price) if inc.SMA.Length() >= inc.Window { reversion := inc.SMA.Last(0) / price inc.Values.Push(reversion) } } func (inc *PMR) Last(i int) float64 { return inc.Values.Last(i) } func (inc *PMR) Index(i int) float64 { return inc.Last(i) } func (inc *PMR) Length() int { return len(inc.Values) } func (inc *PMR) CalculateAndUpdate(allKLines []types.KLine) { if len(inc.Values) == 0 { for _, k := range allKLines { inc.PushK(k) } inc.EmitUpdate(inc.Last(0)) } else { k := allKLines[len(allKLines)-1] inc.PushK(k) inc.EmitUpdate(inc.Last(0)) } } func (inc *PMR) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { if inc.Interval != interval { return } inc.CalculateAndUpdate(window) } func (inc *PMR) Bind(updater indicator.KLineWindowUpdater) { updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) } func (inc *PMR) PushK(k types.KLine) { if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) { return } inc.Update(types.KLineClosePriceMapper(k)) inc.EndTime = k.EndTime.Time() inc.EmitUpdate(inc.Last(0)) } func CalculateKLinesPMR(allKLines []types.KLine, window int) float64 { return pmr(types.MapKLinePrice(allKLines, types.KLineClosePriceMapper), window) } func pmr(prices []float64, window int) float64 { var end = len(prices) - 1 if end == 0 { return prices[0] } reversion := -stat.Mean(prices[end-window:end], nil) / prices[end] return reversion }