On Balance Volume Oscillator
obv_length = 20 | ema_length = 9 Overview
On Balance Volume Oscillator tracks whether each bar closes above or below the previous bar, signs the current volume accordingly, and accumulates those signed values inside a rolling OBV window. It then divides the signed volume sum by the total volume sum over the same window, producing a normalized line that stays bounded even when raw volume changes significantly over time.
A short EMA of that normalized line becomes the signal series. The indicator accepts candles or separate price and volume slices, defaults to the candle close source, and the streaming variant can either ignore invalid bars or reset itself when a NaN update is received.
Defaults: `obv_length = 20` and `ema_length = 9`.
Implementation Examples
Use close and volume slices directly or let the candle input select close plus volume automatically.
use vector_ta::indicators::on_balance_volume_oscillator::{
on_balance_volume_oscillator,
OnBalanceVolumeOscillatorInput,
OnBalanceVolumeOscillatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let output = on_balance_volume_oscillator(&OnBalanceVolumeOscillatorInput::from_slices(
&close,
&volume,
OnBalanceVolumeOscillatorParams {
obv_length: Some(20),
ema_length: Some(9),
},
))?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = on_balance_volume_oscillator(
&OnBalanceVolumeOscillatorInput::with_default_candles(&candles),
)?;
println!("line = {:?}", output.line.last());
println!("signal = {:?}", candle_output.signal.last()); API Reference
Input Methods ▼
OnBalanceVolumeOscillatorInput::from_candles(
&Candles,
"close",
OnBalanceVolumeOscillatorParams,
) -> OnBalanceVolumeOscillatorInput
OnBalanceVolumeOscillatorInput::from_slices(
&[f64],
&[f64],
OnBalanceVolumeOscillatorParams,
) -> OnBalanceVolumeOscillatorInput
OnBalanceVolumeOscillatorInput::with_default_candles(&Candles)
-> OnBalanceVolumeOscillatorInput Parameters Structure ▼
pub struct OnBalanceVolumeOscillatorParams {
pub obv_length: Option<usize>, // default 20
pub ema_length: Option<usize>, // default 9
} Output Structure ▼
pub struct OnBalanceVolumeOscillatorOutput {
pub line: Vec<f64>,
pub signal: Vec<f64>,
} Validation, Warmup & NaNs ▼
- Source and volume arrays must have matching lengths and must not be empty.
obv_lengthandema_lengthmust be greater than0.- The indicator requires a consecutive valid run at least as long as
obv_length. - Warmup for both line and signal starts at the first valid bar and lasts
obv_length - 1bars. - The basic stream ignores invalid bars and returns
None;update_reset_on_nanresets state first. - Batch mode validates the sweep tuples and rejects non-batch kernels.
Builder, Streaming & Batch APIs ▼
OnBalanceVolumeOscillatorBuilder::new()
.obv_length(usize)
.ema_length(usize)
.kernel(Kernel)
.apply(&Candles)
.apply_source(&Candles, "close")
.apply_slices(&[f64], &[f64])
.into_stream()
OnBalanceVolumeOscillatorStream::try_new(params)
stream.update(source, volume) -> Option<(f64, f64)>
stream.update_reset_on_nan(source, volume) -> Option<(f64, f64)>
stream.reset()
OnBalanceVolumeOscillatorBatchBuilder::new()
.obv_length_range(start, end, step)
.ema_length_range(start, end, step)
.kernel(Kernel)
.apply_source(&Candles, "close")
.apply_slices(&[f64], &[f64]) Python Bindings
Python exposes a two-array scalar function, a stream class, and a batch helper. The scalar call returns line and
signal arrays, the stream returns a tuple or None, and the batch helper returns reshaped matrices
together with the swept OBV and EMA window arrays.
from vector_ta import (
on_balance_volume_oscillator,
on_balance_volume_oscillator_batch,
OnBalanceVolumeOscillatorStream,
)
line, signal = on_balance_volume_oscillator(
close,
volume,
obv_length=20,
ema_length=9,
)
stream = OnBalanceVolumeOscillatorStream(20, 9)
point = stream.update(close[-1], volume[-1])
batch = on_balance_volume_oscillator_batch(
close,
volume,
obv_length_range=(10, 30, 10),
ema_length_range=(5, 15, 5),
)
print(batch.keys())
# dict_keys(['line', 'signal', 'obv_lengths', 'ema_lengths', 'rows', 'cols']) JavaScript/WASM Bindings
The WASM layer includes high-level scalar and batch entry points plus low-level allocation and into-buffer helpers. The scalar and batch APIs return plain JS objects, and the low-level APIs write line and signal buffers into caller-managed memory.
import init, {
on_balance_volume_oscillator_js,
on_balance_volume_oscillator_batch_js,
on_balance_volume_oscillator_alloc,
on_balance_volume_oscillator_free,
on_balance_volume_oscillator_into,
on_balance_volume_oscillator_into_host,
on_balance_volume_oscillator_batch_into,
} from "vector-ta-wasm";
await init();
const out = on_balance_volume_oscillator_js(close, volume, 20, 9);
const batch = on_balance_volume_oscillator_batch_js(close, volume, {
obv_length_range: [10, 30, 10],
ema_length_range: [5, 15, 5],
}); CUDA Bindings (Rust)
Additional details for the CUDA bindings can be found inside the VectorTA repository.
Performance Analysis
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)
Related Indicators
Accumulation/Distribution
Technical analysis indicator
Accumulation/Distribution Oscillator
Technical analysis indicator
Aroon
Technical analysis indicator
Aroon Oscillator
Technical analysis indicator
Balance of Power
Technical analysis indicator
Chaikin Flow Oscillator
Technical analysis indicator