Volume Oscillator (VOSC)

Parameters: 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, and short_period ≤ long_period.
  • Input cannot be empty; otherwise VoscError::EmptyData.
  • There must be at least long_period valid values after the first finite input; otherwise VoscError::NotEnoughValidData.
  • If all values are NaN, returns VoscError::AllValuesNaN.
  • Warmup: indices [0 .. first_finite + long_period − 2] are NaN; first valid output at first_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

Comparison:
View:

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

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators