diff --git a/pkg/indicator/atr_callbacks.go b/pkg/indicator/atr_callbacks.go index 67952ad71..addc133bb 100644 --- a/pkg/indicator/atr_callbacks.go +++ b/pkg/indicator/atr_callbacks.go @@ -4,12 +4,12 @@ package indicator import () -func (A *ATR) OnUpdate(cb func(value float64)) { - A.UpdateCallbacks = append(A.UpdateCallbacks, cb) +func (inc *ATR) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) } -func (A *ATR) EmitUpdate(value float64) { - for _, cb := range A.UpdateCallbacks { +func (inc *ATR) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { cb(value) } } diff --git a/pkg/indicator/atrp.go b/pkg/indicator/atrp.go new file mode 100644 index 000000000..f546e04e2 --- /dev/null +++ b/pkg/indicator/atrp.go @@ -0,0 +1,112 @@ +package indicator + +import ( + "math" + "time" + + "github.com/c9s/bbgo/pkg/types" +) + +// ATRP is the average true range percentage +// See also https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/atrp +// +// Calculation: +// +// ATRP = (Average True Range / Close) * 100 +// +//go:generate callbackgen -type ATRP +type ATRP struct { + types.SeriesBase + types.IntervalWindow + PercentageVolatility types.Float64Slice + + PreviousClose float64 + RMA *RMA + + EndTime time.Time + UpdateCallbacks []func(value float64) +} + +func (inc *ATRP) Update(high, low, cloze float64) { + if inc.Window <= 0 { + panic("window must be greater than 0") + } + + if inc.RMA == nil { + inc.SeriesBase.Series = inc + inc.RMA = &RMA{ + IntervalWindow: types.IntervalWindow{Window: inc.Window}, + Adjust: true, + } + inc.PreviousClose = cloze + return + } + + // calculate true range + trueRange := high - low + hc := math.Abs(high - inc.PreviousClose) + lc := math.Abs(low - inc.PreviousClose) + if trueRange < hc { + trueRange = hc + } + if trueRange < lc { + trueRange = lc + } + + // Note: this is the difference from ATR + trueRange = trueRange / inc.PreviousClose * 100.0 + + inc.PreviousClose = cloze + + // apply rolling moving average + inc.RMA.Update(trueRange) + atr := inc.RMA.Last() + inc.PercentageVolatility.Push(atr / cloze) +} + +func (inc *ATRP) Last() float64 { + if inc.RMA == nil { + return 0 + } + return inc.RMA.Last() +} + +func (inc *ATRP) Index(i int) float64 { + if inc.RMA == nil { + return 0 + } + return inc.RMA.Index(i) +} + +func (inc *ATRP) Length() int { + if inc.RMA == nil { + return 0 + } + return inc.RMA.Length() +} + +var _ types.SeriesExtend = &ATRP{} + +func (inc *ATRP) CalculateAndUpdate(kLines []types.KLine) { + for _, k := range kLines { + if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { + continue + } + inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) + } + + inc.EmitUpdate(inc.Last()) + inc.EndTime = kLines[len(kLines)-1].EndTime.Time() +} + +func (inc *ATRP) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.CalculateAndUpdate(window) +} + +func (inc *ATRP) Bind(updater KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} diff --git a/pkg/indicator/atrp_callbacks.go b/pkg/indicator/atrp_callbacks.go new file mode 100644 index 000000000..daaba836d --- /dev/null +++ b/pkg/indicator/atrp_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type ATRP"; DO NOT EDIT. + +package indicator + +import () + +func (inc *ATRP) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *ATRP) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { + cb(value) + } +}