package indicator import "github.com/c9s/bbgo/pkg/types" // Refer: Klinger Oscillator // Refer URL: https://www.investopedia.com/terms/k/klingeroscillator.asp // Explanation: // The Klinger Oscillator is a technical indicator that was developed by Stephen Klinger. // It is based on the assumption that there is a relationship between money flow and price movement in the stock market. // 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. // 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 type KlingerOscillator struct { types.SeriesBase types.IntervalWindow Fast *EWMA Slow *EWMA VF VolumeForce updateCallbacks []func(value float64) } func (inc *KlingerOscillator) Length() int { if inc.Fast == nil || inc.Slow == nil { return 0 } return inc.Fast.Length() } func (inc *KlingerOscillator) Last() float64 { if inc.Fast == nil || inc.Slow == nil { return 0 } return inc.Fast.Last() - inc.Slow.Last() } 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) { if inc.Fast == nil { inc.SeriesBase.Series = inc inc.Fast = &EWMA{IntervalWindow: types.IntervalWindow{Window: 34, Interval: inc.Interval}} inc.Slow = &EWMA{IntervalWindow: types.IntervalWindow{Window: 55, Interval: inc.Interval}} } inc.VF.Update(high, low, cloze, volume) inc.Fast.Update(inc.VF.Value) inc.Slow.Update(inc.VF.Value) } var _ types.SeriesExtend = &KlingerOscillator{} func (inc *KlingerOscillator) PushK(k types.KLine) { inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64(), k.Volume.Float64()) } func (inc *KlingerOscillator) CalculateAndUpdate(allKLines []types.KLine) { if inc.Fast == nil { for _, k := range allKLines { inc.PushK(k) inc.EmitUpdate(inc.Last()) } } else { k := allKLines[len(allKLines)-1] inc.PushK(k) inc.EmitUpdate(inc.Last()) } } func (inc *KlingerOscillator) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { if inc.Interval != interval { return } inc.CalculateAndUpdate(window) } func (inc *KlingerOscillator) Bind(updater KLineWindowUpdater) { updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) } // Utility to hold the state of calculation type VolumeForce struct { dm float64 cm float64 trend float64 lastSum float64 Value float64 } func (inc *VolumeForce) Update(high, low, cloze, volume float64) { if inc.Value == 0 { inc.dm = high - low inc.cm = inc.dm inc.trend = 1. inc.lastSum = high + low + cloze inc.Value = volume * 100. return } trend := 1. if high+low+cloze <= inc.lastSum { trend = -1. } dm := high - low if inc.trend == trend { inc.cm = inc.cm + dm } else { inc.cm = inc.dm + dm } inc.trend = trend inc.lastSum = high + low + cloze inc.dm = dm inc.Value = volume * (2.*(inc.dm/inc.cm) - 1.) * trend * 100. }