Voss Predictive Filter (VOSS)

Parameters: period = 20 | predict = 3 | bandwidth = 0.25

Overview

John Ehlers introduced the Voss Predictive Filter in August 2019, adapting Henning U. Voss's mathematical work on negative delay filters into a practical trading tool. The indicator generates two outputs: a two-pole band-pass filter that tracks cyclical price movements, and a predictive line that anticipates future price direction by subtracting weighted historical values from the current filtered signal. While the filter cannot actually see into the future, its negative group delay provides signals earlier than conventional indicators, potentially offering traders an edge through advanced warning of price turns. Ehlers reduced Voss's complex mathematical framework into just six lines of code, making sophisticated signal processing accessible to traders. The predictive component works by analyzing the cyclic nature of price movements and projecting where the cycle should go next based on its current phase. Traders typically watch for crossovers between the predictive line and the filter line to identify potential entry and exit points, with the predictive line leading the filter during trend changes.

Defaults: period=20, predict=3, bandwidth=0.25.

Implementation Examples

Compute VOSS from prices or candles:

use vectorta::indicators::voss::{voss, VossInput, VossParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From a price slice
let prices = vec![100.0, 101.5, 103.0, 102.0, 104.5];
let params = VossParams { period: Some(20), predict: Some(3), bandwidth: Some(0.25) };
let input = VossInput::from_slice(&prices, params);
let out = voss(&input)?; // out.voss and out.filt (Vec<f64>)

// From candles with defaults (period=20, predict=3, bandwidth=0.25; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = VossInput::with_default_candles(&candles);
let out = voss(&input)?;

// Access both outputs
for (v, f) in out.voss.iter().zip(out.filt.iter()) {
    println!("voss: {v}, filt: {f}");
}

API Reference

Input Methods
// From price slice
VossInput::from_slice(&[f64], VossParams) -> VossInput

// From candles with custom source
VossInput::from_candles(&Candles, &str, VossParams) -> VossInput

// From candles with defaults (close, period=20, predict=3, bandwidth=0.25)
VossInput::with_default_candles(&Candles) -> VossInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct VossParams {
    pub period: Option<usize>,   // Default: 20
    pub predict: Option<usize>,  // Default: 3
    pub bandwidth: Option<f64>,  // Default: 0.25
}
Output Structure
#[derive(Debug, Clone)]
pub struct VossOutput {
    pub voss: Vec<f64>, // predictive series
    pub filt: Vec<f64>, // two-pole filter
}
Validation, Warmup & NaNs
  • period > 0; period <= data.len(). Otherwise VossError::InvalidPeriod.
  • Input must contain at least one finite value; else VossError::AllValuesNaN.
  • Warmup length = first_non_nan + max(period, 5, 3×predict); if not enough valid points, VossError::NotEnoughValidData.
  • Outputs are NaN during warmup (prefix is filled via allocation helpers).
  • Streaming: VossStream::update returns None until warmup completes; then (voss, filt).
Error Handling
use vectorta::indicators::voss::{voss, VossError};

match voss(&input) {
    Ok(out) => use_outputs(out.voss, out.filt),
    Err(VossError::EmptyInputData) => eprintln!("Input data is empty"),
    Err(VossError::AllValuesNaN) => eprintln!("All input values are NaN"),
    Err(VossError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for data length {}", period, data_len),
    Err(VossError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points, only {} found", needed, valid),
}

Python Bindings

Basic Usage

Calculate VOSS (returns both voss and filt):

import numpy as np
from vectorta import voss

prices = np.array([100.0, 101.5, 103.0, 102.0, 104.5], dtype=np.float64)

# Defaults: period=20, predict=3, bandwidth=0.25
voss_vals, filt_vals = voss(prices)

# Custom parameters and optional kernel
voss_vals, filt_vals = voss(prices, period=20, predict=3, bandwidth=0.25, kernel="auto")

print(voss_vals.shape, filt_vals.shape)
Streaming Real-time Updates
from vectorta import VossStream

stream = VossStream(period=20, predict=3, bandwidth=0.25)
for price in price_feed:
    pair = stream.update(price)  # None during warmup
    if pair is not None:
        voss_val, filt_val = pair
        consume(voss_val, filt_val)
Batch Parameter Sweep
import numpy as np
from vectorta import voss_batch

prices = np.array([...], dtype=np.float64)

res = voss_batch(
    prices,
    period_range=(10, 30, 5),
    predict_range=(1, 4, 1),
    bandwidth_range=(0.10, 0.40, 0.10),
    kernel="auto"
)

# res is a dict with 'voss', 'filt', 'periods', 'predicts', 'bandwidths'
print(res['voss'].shape, res['filt'].shape)
CUDA Acceleration

CUDA support for VOSS is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated VOSS calculations
# (patterns will mirror other indicators in this project)

JavaScript/WASM Bindings

Basic Usage

Calculate VOSS in JavaScript/TypeScript:

import { voss_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 101.5, 103, 102, 104.5]);

// Returns an object with { voss, filt }
const out = await voss_js(prices, 20, 3, 0.25);
console.log(out.voss.length, out.filt.length);
Memory-Efficient Operations

Zero‑copy in/out using WASM memory and voss_into:

import { voss_alloc, voss_free, voss_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* your data */]);
const n = prices.length;

const inPtr = voss_alloc(n);
const vossPtr = voss_alloc(n);
const filtPtr = voss_alloc(n);

// Copy input into WASM memory
new Float64Array(memory.buffer, inPtr, n).set(prices);

// Args: in_ptr, voss_ptr, filt_ptr, len, period, predict, bandwidth
await voss_into(inPtr, vossPtr, filtPtr, n, 20, 3, 0.25);

// Read results (slice to copy)
const vossVals = new Float64Array(memory.buffer, vossPtr, n).slice();
const filtVals = new Float64Array(memory.buffer, filtPtr, n).slice();

voss_free(inPtr, n);
voss_free(vossPtr, n);
voss_free(filtPtr, n);
Batch Processing

Compute many parameter combos efficiently:

import { voss_batch as voss_batch_js, voss_batch_metadata_js } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);

// Metadata: triples [period, predict, bandwidth, ...]
const md = await voss_batch_metadata_js(10, 30, 5, 1, 4, 1, 0.1, 0.4, 0.1);

// Unified batch API (returns { voss, filt, combos, rows, cols })
const out = await voss_batch_js(prices, {
  period_range: [10, 30, 5],
  predict_range: [1, 4, 1],
  bandwidth_range: [0.1, 0.4, 0.1]
});

console.log(out.rows, out.cols, out.combos.length);

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators