diff --git a/pkg/indicator/obv.go b/pkg/indicator/obv.go new file mode 100644 index 000000000..507eeea9a --- /dev/null +++ b/pkg/indicator/obv.go @@ -0,0 +1,78 @@ +package indicator + +import ( + "time" + + "github.com/c9s/bbgo/pkg/types" +) + +/* +obv implements on-balance volume indicator + +On-Balance Volume (OBV) Definition +- https://www.investopedia.com/terms/o/onbalancevolume.asp +*/ +//go:generate callbackgen -type OBV +type OBV struct { + types.IntervalWindow + Values Float64Slice + PrePrice float64 + + EndTime time.Time + UpdateCallbacks []func(value float64) +} + +func (inc *OBV) update(kLine types.KLine, priceF KLinePriceMapper) { + price := priceF(kLine) + volume := kLine.Volume + + if len(inc.Values) == 0 { + inc.PrePrice = price + inc.Values.Push(volume) + return + } + + preOBV := inc.Values[len(inc.Values)-1] + + var sign float64 = 0.0 + if volume > inc.PrePrice { + sign = 1.0 + } else if volume < inc.PrePrice { + sign = -1.0 + } + obv := preOBV + sign*volume + inc.Values.Push(obv) +} + +func (inc *OBV) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *OBV) calculateAndUpdate(kLines []types.KLine) { + var priceF = KLineClosePriceMapper + + for i, k := range kLines { + if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) { + continue + } + + inc.update(k, priceF) + inc.EmitUpdate(inc.Last()) + inc.EndTime = kLines[i].EndTime + } + +} +func (inc *OBV) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *OBV) Bind(updater KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} diff --git a/pkg/indicator/obv_callbacks.go b/pkg/indicator/obv_callbacks.go new file mode 100644 index 000000000..b0897152c --- /dev/null +++ b/pkg/indicator/obv_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type OBV"; DO NOT EDIT. + +package indicator + +import () + +func (inc *OBV) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *OBV) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { + cb(value) + } +}