On Balance Volume Oscillator

Parameters: 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_length and ema_length must be greater than 0.
  • 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 - 1 bars.
  • The basic stream ignores invalid bars and returns None; update_reset_on_nan resets 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

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators