Klinger Volume Oscillator (Stephen Klinger, 1997) — the difference of
two EMAs of a volume force that carries its own trend state. Kernel
K6 feeding the K2 engine.
Appends two columns (kvo / kvoSignal at the default prefix). Reads
four — high, low, close and volume — each named by an option
defaulting to its DEFAULT_OHLCV name.
Which Klinger — F-AMBIG, and the fork is named
The corpus assessment flags this study F-AMBIG and it earns it: two
different formulas ship under the name.
This study is the original, as Klinger published it and as
StockCharts documents it: the volume force above, with the trend state
and the cumulative measurement. The |2 × (dm/cm − 1)| factor is the
part every restatement garbles; the reading taken here is
2 × ((dm/cm) − 1) under the absolute value, not2 × (dm/cm) − 1.
The two differ by a constant inside the modulus and therefore by a
different amount on every bar; the oracle carries a case that separates
them.
The named alternative is TradingView's ta.kvo, which drops the
dm/cm factor entirely: its volume force is just ±volume × 100 on the
sign of the HLC change, so it is an EMA-pair oscillator of signed volume
and no state machine at all. It is not implemented here, and it is not
an option on this study — the two are different indicators that share a
name, and a variant knob would hide that. Measured on the oracle's 80
bars, the two disagree by more than the oscillator's own range: the
simplified form's kvo spans a different order of magnitude entirely
(the generator prints both).
The EMAs are pond's — first-sample seed, α = 2/(span+1), the
movingAverageValues array door — matching macd and for the
same reason (making them TA-Lib-seeded here would make this study's EMAs
disagree with ema() inside the package). TA-Lib has no Klinger, so
nothing arbitrates it.
Warm-up — per column
The volume force starts at bar 1 (it needs a predecessor for the trend
comparison and for dm[i−1]), so on gap-free input the oscillator starts
at bar slowPeriod and the signal at slowPeriod + signalPeriod − 1. Each
column is emitted where it is genuinely defined rather than both waiting
for the slower — the same per-column warm-up macd uses.
Edges
Scale behaviour is mixed, and the mixture is the point. The volume
force is volume × (a ratio of ranges) × ±1 × 100, so the study is
linear in volume (double every volume and both columns double) but
invariant to a price scale and a price shift: dm/cm is a ratio of
ranges, so a scale cancels and a shift never reaches it, and the trend
flag compares two high + low + close sums, which a positive scale and
a +3c on both sides both preserve. Adding a constant to every volume
is not a no-op, unlike negativeVolumeIndex, which only
compares volumes — the force multiplies by one. All four are asserted as
property tests.
A zero-range leg (cm = 0, which needs every bar in the leg to have
high === low) is a genuine 0/0 and reads undefined for that bar;
the EMAs then propagate it, since a recursion has no state to carry
across a hole. There is no guard for it: cm is a sum of
non-negative ranges, so a zero cm forces a zero dm with it and the
ratio is NaN on its own — a guard would be dead code.
A missing cell in any of the four inputs resets the K6 machine
([PND-SFOLD]): the volume force is undefined for that bar and the
trend/cm state re-seeds from the next complete pair. The EMAs
downstream nonetheless carry the undefined forward — pond's EMA skips
a missing bar in its recursion, so the oscillator resumes, but the two
spans resume at different points, which is why an interior gap is best
filled before running this.
Klinger Volume Oscillator (Stephen Klinger, 1997) — the difference of two EMAs of a volume force that carries its own trend state. Kernel K6 feeding the K2 engine.
Appends two columns (
kvo/kvoSignalat the default prefix). Reads four — high, low, close and volume — each named by an option defaulting to itsDEFAULT_OHLCVname.Which Klinger — F-AMBIG, and the fork is named
The corpus assessment flags this study
F-AMBIGand it earns it: two different formulas ship under the name.|2 × (dm/cm − 1)|factor is the part every restatement garbles; the reading taken here is2 × ((dm/cm) − 1)under the absolute value, not2 × (dm/cm) − 1. The two differ by a constant inside the modulus and therefore by a different amount on every bar; the oracle carries a case that separates them.ta.kvo, which drops thedm/cmfactor entirely: its volume force is just±volume × 100on the sign of the HLC change, so it is an EMA-pair oscillator of signed volume and no state machine at all. It is not implemented here, and it is not an option on this study — the two are different indicators that share a name, and avariantknob would hide that. Measured on the oracle's 80 bars, the two disagree by more than the oscillator's own range: the simplified form'skvospans a different order of magnitude entirely (the generator prints both).The EMAs are pond's — first-sample seed,
α = 2/(span+1), the movingAverageValues array door — matching macd and for the same reason (making them TA-Lib-seeded here would make this study's EMAs disagree withema()inside the package). TA-Lib has no Klinger, so nothing arbitrates it.Warm-up — per column
The volume force starts at bar 1 (it needs a predecessor for the trend comparison and for
dm[i−1]), so on gap-free input the oscillator starts at barslowPeriodand the signal atslowPeriod + signalPeriod − 1. Each column is emitted where it is genuinely defined rather than both waiting for the slower — the same per-column warm-up macd uses.Edges
volume × (a ratio of ranges) × ±1 × 100, so the study is linear in volume (double every volume and both columns double) but invariant to a price scale and a price shift:dm/cmis a ratio of ranges, so a scale cancels and a shift never reaches it, and the trend flag compares twohigh + low + closesums, which a positive scale and a+3con both sides both preserve. Adding a constant to every volume is not a no-op, unlike negativeVolumeIndex, which only compares volumes — the force multiplies by one. All four are asserted as property tests.cm = 0, which needs every bar in the leg to havehigh === low) is a genuine0/0and readsundefinedfor that bar; the EMAs then propagate it, since a recursion has no state to carry across a hole. There is no guard for it:cmis a sum of non-negative ranges, so a zerocmforces a zerodmwith it and the ratio isNaNon its own — a guard would be dead code.undefinedfor that bar and the trend/cmstate re-seeds from the next complete pair. The EMAs downstream nonetheless carry theundefinedforward — pond's EMA skips a missing bar in its recursion, so the oscillator resumes, but the two spans resume at different points, which is why an interior gap is best filled before running this.