Klinger Volume Oscillator

Klinger Volume Oscillator

All functions · nseries package

KlingerVolumeForce

func (s Series) KlingerVolumeForce(h, l, c, v Series) Series

KlingerVolumeForce returns the volume force of Stephen Klinger’s Volume Oscillator for the bars with high h, low l, close c and volume v. The receiver is not used (as in AccumDist, ChaikinOsc, MFI and TrueRange). The inputs h, l, c and v must have the same length M; otherwise the result is an empty, non-nil Series. Otherwise the result is a non-nil Series of length M (non-nil also when M is 0). The inputs are not modified and the result is freshly allocated.

The construction is Klinger’s (“Identifying Trends With Volume Analysis”, Technical Analysis of Stocks & Commodities, December 1997). The trend T is +1 when the sum high + low + close rises from the previous bar and -1 when it falls, and it is held when the sum is unchanged. The daily measurement is DM = high - low. The cumulative measurement CM adds up the DM of the current trend and restarts on a trend change, when it becomes the DM of the previous bar plus the DM of the current bar, so the first bar of the new trend is included. The volume force is

VF = v |2 DM/CM - 1| T 100

with Klinger’s factor 100. KlingerVolumeOscillator is the difference of two exponential moving averages of this series.

The warm-up reads 0, not NaN: the bars before the first valid index have no trend yet and read +0. The first valid index is the first bar whose sum high + low + close differs from the previous bar’s, normally bar 1.

Non-finite values: if h, l, c or v is NaN or infinite at a bar, or the sum high + low + close, DM, CM or the force overflows to an infinity there, that bar and every later bar are NaN. Earlier bars are unchanged by a later stop.

If CM is exactly 0 the ratio DM/CM is taken as 0. (The TradeStation code in the Traders’ Tips of December 1997 assigns the force only where CM <> 0, so it keeps the previous force there; Tulip gives NaN there and on every later bar.) The sign is applied by exact negation, so the force can be -0 (for example on a falling bar with DM/CM = 1/2 and a positive volume).

If the sum is unchanged the trend is held, as in Klinger’s text and in Tulip. (His spreadsheet, the TradeStation code of the December 1997 Traders’ Tips and the TradingView help page use -1 instead.)

Klinger’s defaults are 34 and 55 for the two averages and 13 for his trigger line; they are not encoded here. Both averages of KlingerVolumeOscillator are seeded with the first force, as Klinger’s spreadsheet and Tulip seed them (XAverage, TradingView’s ta.ema and pandas-ta seed with a simple average). So, with aF = 2 / (float64(fast) + 1) and aS = 2 / (float64(slow) + 1), the oscillator from the first valid index up to the bar before its first NaN is vf.XAverageAlpha(aF).Sub(vf.XAverageAlpha(aS)), where vf is the force over those bars, bit for bit.

KlingerVolumeOscillator gives the recipes for Klinger’s trigger line, the simplified signed-volume form of TradingView and pandas-ta, and the cumulative KVO.

The force is latched: the trend and CM carry over from bar to bar and a non-finite value is final, so on a later slice of the inputs the force can differ from the full inputs’ force on its first bars, until a trend change restarts CM in both; after that the two agree (unless a non-finite value before the slice stopped the full inputs).

Sources: Klinger, “Identifying Trends With Volume Analysis”, Technical Analysis of Stocks & Commodities, December 1997; Tulip Indicators, kvo.

KlingerVolumeOscillator

func (s Series) KlingerVolumeOscillator(h, l, c, v Series, fast, slow int) Series

KlingerVolumeOscillator returns Stephen Klinger’s Volume Oscillator: the difference of a fast and a slow exponential moving average of the volume force of KlingerVolumeForce, for the bars with high h, low l, close c and volume v. The receiver is not used (as in AccumDist, ChaikinOsc, MFI and TrueRange). The inputs h, l, c and v must have the same length M; otherwise the result is an empty, non-nil Series, whatever fast and slow are. The parameters are valid if 1 <= fast <= slow (there is no upper bound and no length requirement); invalid parameters give M values of +0. Otherwise the result is a non-nil Series of length M (non-nil also when M is 0). The inputs are not modified and the result is freshly allocated.

Klinger’s defaults are fast = 34 and slow = 55, with 13 for his trigger line; they are not encoded here.

The construction is Klinger’s (“Identifying Trends With Volume Analysis”, Technical Analysis of Stocks & Commodities, December 1997). The trend T is +1 when the sum high + low + close rises from the previous bar and -1 when it falls, and it is held when the sum is unchanged. The daily measurement is DM = high - low. The cumulative measurement CM adds up the DM of the current trend and restarts on a trend change, when it becomes the DM of the previous bar plus the DM of the current bar, so the first bar of the new trend is included. The volume force is

VF = v |2 DM/CM - 1| T 100

with Klinger’s factor 100. With aF = 2 / (float64(fast) + 1) and aS = 2 / (float64(slow) + 1), each average adds the force times its constant to the previous average times one minus that constant (a constant of 1 copies the force). Both averages are seeded with the first force, as Klinger’s spreadsheet and Tulip seed them (XAverage, TradingView’s ta.ema and pandas-ta seed with a simple average). So from the first valid index up to the bar before the first NaN the result is vf.XAverageAlpha(aF).Sub(vf.XAverageAlpha(aS)), where vf is the force over those bars, bit for bit; when fast equals slow every valid bar reads +0.

The warm-up reads 0, not NaN: the bars before the first valid index have no trend yet and read +0, and so does the first valid index itself, because both averages start from the first force. The first valid index is the first bar whose sum high + low + close differs from the previous bar’s, normally bar 1.

Non-finite values: if h, l, c or v is NaN or infinite at a bar, or the sum high + low + close, DM, CM, the force, an average or the oscillator overflows to an infinity there, that bar and every later bar are NaN. Earlier bars are unchanged by a later stop.

If CM is exactly 0 the ratio DM/CM is taken as 0. (The TradeStation code in the Traders’ Tips of December 1997 assigns the force only where CM <> 0, so it keeps the previous force there; Tulip gives NaN there and on every later bar.)

If the sum is unchanged the trend is held, as in Klinger’s text and in Tulip. (His spreadsheet, the TradeStation code of the December 1997 Traders’ Tips and the TradingView help page use -1 instead.)

Klinger’s trigger line is a 13-bar exponential average of the oscillator:

kvo := c.KlingerVolumeOscillator(h, l, c, v, 34, 55)
trigger := kvo.XAverageAlpha(2.0 / 14)

Because the oscillator reads 0 up to the first valid index, so does the trigger, and from that bar on it is the same average started from a seed of 0, which is how Klinger’s spreadsheet seeds its trigger. His spreadsheet steps with the rounded constant 0.143, M3 = M2 + 0.143 (L3 - M2), so its values differ from these.

TradingView’s built-in Klinger oscillator and the kvo of pandas-ta compute a simplified form with signed volume and no DM or CM. With

tp := h.Add(l).Add(c).Div(3)
d := tp.Momentum(1).SetN(1, math.NaN())

TradingView’s signed volume is sv := v.Mul(d.GTE(0).Mul(2).Sub(1)) (plus volume on an unchanged bar, minus volume on bar 0, where its change is na) and pandas-ta’s is sv := v.Mul(d.GT(0).Sub(d.LT(0)))[1:] (0 on an unchanged bar; pandas-ta leaves out bar 0, so its result starts at bar 1). The oscillator is then sv.XAverage(34).Sub(sv.XAverage(55)); their averages are seeded with a simple average, as XAverage is, and agree with it to rounding from the slow average’s seed bar. Before that bar they give no value while XAverage passes its input through, so mask those bars.

Klinger converts the oscillator to a cumulative KVO for longer horizons (his Figure 4) but does not print the formula. A running total is kvo.CumSum(), as in the SmarTrader code of the December 1997 Traders’ Tips; the TradeStation code there sums over a rolling window of Smooth bars, kvo.Sum(n), and sums its trigger the same way.

Where the inputs are finite, the sum changes between bars 0 and 1 and CM is not 0, the oscillator from bar 1 is the kvo of Tulip Indicators to rounding (Tulip’s output starts at bar 1): Tulip’s averaging step (vf-e)*a + e rounds differently from the step used here. Tulip gives NaN from the first bar where CM is 0 on, starts its trend as rising when the sums of bars 0 and 1 are equal, does not stop at a non-finite value, and its step can overflow near the largest floats where this one does not.

The method is recursive and latched: each bar depends on all the bars before it and a non-finite value is final, so the method on a later slice of the inputs gives different values from the same bars of the full inputs.

Sources: Klinger, “Identifying Trends With Volume Analysis”, Technical Analysis of Stocks & Commodities, December 1997; Traders’ Tips, Technical Analysis of Stocks & Commodities, December 1997; Tulip Indicators, kvo.