Klinger Volume Oscillator (KVO)
short_period = 2 | long_period = 5 Overview
The Klinger Volume Oscillator reveals the true force behind price movements by converting raw volume into directional Volume Force that accounts for the relationship between closing prices and the daily trading range, then comparing short and long term trends in this force to identify accumulation and distribution patterns. KVO calculates a cumulative measure that tracks whether each day's typical price moves higher or lower than the previous day, then multiplies this trend direction by volume weighted according to the relationship between price movement and trading range. The difference between a fast and slow exponential moving average of this Volume Force creates an oscillator that swings above zero during accumulation phases when smart money builds positions, and below zero during distribution when institutions unload holdings. Traders watch for KVO crossing above zero as confirmation that buying pressure dominates selling pressure, particularly powerful when price breaks resistance with the oscillator already positive. Divergences between KVO and price provide early warning signals, as rising KVO during price declines reveals hidden accumulation before reversals, while falling KVO during rallies exposes distribution beneath surface strength. The indicator excels at confirming breakouts by verifying that volume flow supports the price move, distinguishing genuine breakouts backed by institutional participation from false moves driven by retail speculation alone.
Implementation Examples
Compute KVO from OHLCV slices or candle data:
use vectorta::indicators::kvo::{kvo, KvoInput, KvoParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// From OHLCV slices
let high = vec![/* f64 */];
let low = vec![/* f64 */];
let close = vec![/* f64 */];
let volume = vec![/* f64 */];
let params = KvoParams { short_period: Some(2), long_period: Some(5) };
let input = KvoInput::from_slices(&high, &low, &close, &volume, params);
let out = kvo(&input)?; // out.values: Vec<f64>
// From Candles with defaults (short=2, long=5)
let candles: Candles = read_candles_from_csv("data/ohlcv.csv")?;
let input = KvoInput::with_default_candles(&candles);
let out = kvo(&input)?; API Reference
Input Methods ▼
// From Candles
KvoInput::from_candles(&Candles, KvoParams) -> KvoInput
// From OHLCV slices
KvoInput::from_slices(&[f64], &[f64], &[f64], &[f64], KvoParams) -> KvoInput
// With default parameters (short=2, long=5)
KvoInput::with_default_candles(&Candles) -> KvoInput Parameters Structure ▼
pub struct KvoParams {
pub short_period: Option<usize>, // Default: 2
pub long_period: Option<usize>, // Default: 5
} Output Structure ▼
pub struct KvoOutput {
pub values: Vec<f64>, // Oscillator values: EMA_short(VF) - EMA_long(VF)
} Validation, Warmup & NaNs ▼
short_period ≥ 1andlong_period ≥ short_period; elseKvoError::InvalidPeriod { short, long }.- All of
high/low/close/volumemust be non-empty; otherwiseKvoError::EmptyData. - Find first index where all four inputs are finite. If fewer than 2 valid points thereafter:
KvoError::NotEnoughValidData { valid }. - Outputs at indices
0..=first_valid_idxareNaN. First computed value appears atfirst_valid_idx + 1. - If no finite inputs exist:
KvoError::AllValuesNaN. - Zero‑copy APIs validate destination length and may return
KvoError::OutputLenMismatch { got, expected }.
Error Handling ▼
use vectorta::indicators::kvo::{kvo, KvoError};
match kvo(&input) {
Ok(output) => consume(output.values),
Err(KvoError::EmptyData) => eprintln!("OHLCV data is empty"),
Err(KvoError::InvalidPeriod { short, long }) =>
eprintln!("Invalid periods: short={}, long={}", short, long),
Err(KvoError::NotEnoughValidData { valid }) =>
eprintln!("Not enough valid points after first finite index: {}", valid),
Err(KvoError::AllValuesNaN) => eprintln!("All inputs are NaN"),
Err(KvoError::OutputLenMismatch { got, expected }) =>
eprintln!("Output buffer length {} != {}", got, expected),
} Python Bindings
Basic Usage ▼
Compute KVO from NumPy arrays (defaults: short=2, long=5):
import numpy as np
from vectorta import kvo
# OHLCV as NumPy arrays
high = np.array([...], dtype=np.float64)
low = np.array([...], dtype=np.float64)
close = np.array([...], dtype=np.float64)
volume = np.array([...], dtype=np.float64)
# Defaults
vals = kvo(high, low, close, volume)
# Custom params and kernel
vals = kvo(high, low, close, volume, short_period=3, long_period=10, kernel="auto")
print(vals.shape) # same length as inputs Streaming Real-time Updates ▼
from vectorta import KvoStream
stream = KvoStream(short_period=2, long_period=5)
for h, l, c, v in ohlcv_iter:
val = stream.update(h, l, c, v)
if val is not None:
use(val) Batch Parameter Optimization ▼
import numpy as np
from vectorta import kvo_batch
high, low, close, volume = ... # NumPy arrays
results = kvo_batch(
high, low, close, volume,
short_range=(2, 6, 1),
long_range=(5, 10, 1),
kernel="auto"
)
# results is a dict with 'values' [rows x cols], 'shorts', 'longs'
vals_2d = results['values']
shorts = results['shorts']
longs = results['longs'] CUDA Acceleration ▼
CUDA support for KVO is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.
# Coming soon: CUDA-accelerated KVO calculations
# See other indicators for CUDA patterns once available. JavaScript/WASM Bindings
Basic Usage ▼
Compute KVO using the WASM package:
import { kvo_js } from 'vectorta-wasm';
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const close = new Float64Array([/* ... */]);
const volume = new Float64Array([/* ... */]);
const values = kvo_js(high, low, close, volume, 2, 5); Memory-Efficient Operations ▼
Zero-copy into pre-allocated buffers:
import { kvo_alloc, kvo_free, kvo_into, memory } from 'vectorta-wasm';
const len = high.length;
const hPtr = kvo_alloc(len), lPtr = kvo_alloc(len), cPtr = kvo_alloc(len), vPtr = kvo_alloc(len);
const oPtr = kvo_alloc(len);
new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);
new Float64Array(memory.buffer, vPtr, len).set(volume);
// kvo_into(high_ptr, low_ptr, close_ptr, volume_ptr, out_ptr, len, short, long)
kvo_into(hPtr, lPtr, cPtr, vPtr, oPtr, len, 2, 5);
const out = new Float64Array(memory.buffer, oPtr, len).slice();
[hPtr, lPtr, cPtr, vPtr, oPtr].forEach(p => kvo_free(p, len)); Batch Processing ▼
import { kvo_batch_js } from 'vectorta-wasm';
const cfg = { short_period_range: [2, 6, 1], long_period_range: [5, 10, 1] };
const result = kvo_batch_js(high, low, close, volume, cfg);
// result: { values: number[], combos: {short_period?: number, long_period?: number}[], rows: number, cols: number }
// values is a flat row-major array (rows x cols)
console.log(result.rows, result.cols, result.combos.length); Performance Analysis
Across sizes, Rust CPU runs about 1.10× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Accumulation/Distribution
Technical analysis indicator
Accumulation/Distribution Oscillator
Technical analysis indicator
Balance of Power
Technical analysis indicator
Chaikin Flow Oscillator
Technical analysis indicator
Elder Force Index
Technical analysis indicator
Ease of Movement
Technical analysis indicator