Volume Oscillator (VOSC)
short_period = 2 | long_period = 5 Overview
The Volume Oscillator measures the difference between two volume moving averages to reveal whether volume trends are strengthening or weakening. By calculating the percentage difference between fast and slow volume averages, traders can identify volume surges that confirm price movements or warn of potential reversals. When volume oscillates above zero, short period volume exceeds long period volume, signaling increased market participation. Conversely, readings below zero suggest declining interest as recent volume falls below its longer average. The indicator proves especially valuable for confirming breakouts because genuine moves typically coincide with expanding volume oscillator values. Unlike absolute volume measurements that vary widely between different assets, the percentage format normalizes readings for easy comparison across markets. Most traders watch for divergences between price trends and volume oscillator direction, as weakening volume often precedes trend exhaustion.
Defaults: short=2, long=5. Source for candles: volume.
Implementation Examples
Get started with VOSC in a few lines:
use vectorta::indicators::vosc::{vosc, VoscInput, VoscParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with a volume slice
let volumes = vec![1200.0, 1500.0, 1300.0, 1800.0, 2000.0, 1700.0];
let params = VoscParams { short_period: Some(2), long_period: Some(5) };
let input = VoscInput::from_slice(&volumes, params);
let result = vosc(&input)?;
// Using with Candles (defaults: short=2, long=5; source="volume")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = VoscInput::with_default_candles(&candles);
let result = vosc(&input)?;
// Access the VOSC values (percent)
for v in result.values { println!("VOSC: {}", v); } API Reference
Input Methods ▼
// From volume slice
VoscInput::from_slice(&[f64], VoscParams) -> VoscInput
// From candles with custom source (e.g., "volume")
VoscInput::from_candles(&Candles, &str, VoscParams) -> VoscInput
// From candles with defaults (source="volume", 2/5 periods)
VoscInput::with_default_candles(&Candles) -> VoscInput Parameters Structure ▼
pub struct VoscParams {
pub short_period: Option<usize>, // Default: 2
pub long_period: Option<usize>, // Default: 5
} Output Structure ▼
pub struct VoscOutput {
pub values: Vec<f64>, // VOSC percent values (length matches input)
} Validation, Warmup & NaNs ▼
short_period > 0,long_period > 0, andshort_period ≤ long_period.- Input cannot be empty; otherwise
VoscError::EmptyData. - There must be at least
long_periodvalid values after the first finite input; otherwiseVoscError::NotEnoughValidData. - If all values are
NaN, returnsVoscError::AllValuesNaN. - Warmup: indices
[0 .. first_finite + long_period − 2]areNaN; first valid output atfirst_finite + long_period − 1.
Error Handling ▼
use vectorta::indicators::vosc::{vosc, VoscError};
match vosc(&input) {
Ok(output) => process(output.values),
Err(VoscError::EmptyData) => eprintln!("vosc: empty input"),
Err(VoscError::InvalidShortPeriod { period, data_len }) => {
eprintln!("vosc: invalid short period: {} (len={})", period, data_len)
}
Err(VoscError::InvalidLongPeriod { period, data_len }) => {
eprintln!("vosc: invalid long period: {} (len={})", period, data_len)
}
Err(VoscError::ShortPeriodGreaterThanLongPeriod) => {
eprintln!("vosc: short_period > long_period")
}
Err(VoscError::NotEnoughValidData { needed, valid }) => {
eprintln!("Need {} valid points after first finite, have {}", needed, valid)
}
Err(VoscError::AllValuesNaN) => eprintln!("vosc: all values are NaN"),
} Python Bindings
Basic Usage ▼
Calculate VOSC on NumPy arrays (defaults: short=2, long=5):
import numpy as np
from vectorta import vosc
volumes = np.array([1200.0, 1500.0, 1300.0, 1800.0])
# Defaults (2/5)
vals = vosc(volumes)
# Custom parameters
vals = vosc(volumes, short_period=3, long_period=10)
# Optional kernel ("auto", "avx2", "avx512" if available)
vals = vosc(volumes, short_period=2, long_period=5, kernel="auto")
print(vals) Streaming Real-time Updates ▼
from vectorta import VoscStream
stream = VoscStream(short_period=2, long_period=5)
for vol in volume_feed:
value = stream.update(vol)
if value is not None:
print("VOSC:", value) Batch Parameter Optimization ▼
Test multiple parameter combinations:
import numpy as np
from vectorta import vosc_batch
volumes = np.array([...])
short_range = (2, 6, 1)
long_range = (5, 12, 1)
results = vosc_batch(
volumes,
short_period_range=short_range,
long_period_range=long_range,
kernel="auto"
)
print(results["values"].shape) # (num_combos, len(volumes))
print(results["short_periods"]) # tested shorts
print(results["long_periods"]) # tested longs CUDA Acceleration ▼
CUDA support for VOSC is coming soon. The API will mirror other CUDA-enabled indicators.
JavaScript/WASM Bindings
Basic Usage ▼
import { vosc_js } from 'vectorta-wasm';
const volumes = new Float64Array([1200, 1500, 1300, 1800, 2000]);
const result = vosc_js(volumes, 2, 5); // short=2, long=5
console.log('VOSC values:', result); Memory-Efficient Operations ▼
Use zero-copy operations for large datasets:
import { vosc_alloc, vosc_free, vosc_into, memory } from 'vectorta-wasm';
const volumes = new Float64Array([/* your data */]);
const length = volumes.length;
const inPtr = vosc_alloc(length);
const outPtr = vosc_alloc(length);
new Float64Array(memory.buffer, inPtr, length).set(volumes);
// Args: in_ptr, out_ptr, len, short_period, long_period
vosc_into(inPtr, outPtr, length, 2, 5);
const values = new Float64Array(memory.buffer, outPtr, length).slice();
vosc_free(inPtr, length);
vosc_free(outPtr, length);
console.log('VOSC values:', values); Batch Processing ▼
Evaluate multiple parameter combinations in one call:
import { vosc_batch_js } from 'vectorta-wasm';
const volumes = new Float64Array([/* historical volumes */]);
const config = {
short_period_range: [2, 6, 1],
long_period_range: [5, 12, 1],
};
const out = vosc_batch_js(volumes, config);
// out: { values: Float64Array, combos: Array<{short_period?: number, long_period?: number}>, rows: number, cols: number }
// Reshape values into a matrix of [rows x cols]
const matrix = [] as number[][];
for (let r = 0; r < out.rows; r++) {
const start = r * out.cols;
const end = start + out.cols;
matrix.push(Array.from(out.values.slice(start, end)));
}
console.log('Combos:', out.combos);
console.log('Row 0 values:', matrix[0]); Performance Analysis
Across sizes, Rust CPU runs about 1.01× 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
Detrended Oscillator
Technical analysis indicator
Elder Force Index
Technical analysis indicator