Voss Predictive Filter (VOSS)
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 vector_ta::indicators::voss::{voss, VossInput, VossParams};
use vector_ta::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(). OtherwiseVossError::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
NaNduring warmup (prefix is filled via allocation helpers). - Streaming:
VossStream::updatereturnsNoneuntil warmup completes; then(voss, filt).
Error Handling ▼
use vector_ta::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 vector_ta 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 vector_ta 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 vector_ta 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 helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).
import numpy as np
from vector_ta import voss_cuda_batch_dev, voss_cuda_many_series_one_param_dev
# One series (float32)
data_f32 = np.asarray(load_data(), dtype=np.float32)
dev = voss_cuda_batch_dev(
data_f32=data_f32,
period=(5, 30, 5),
predict=(2, 20, 2),
bandwidth=(0.5, 2.0, 0.5),
device_id=0,
)
# Many series (time-major)
data_tm_f32 = np.asarray(load_data_time_major_matrix(), dtype=np.float32)
dev_tm = voss_cuda_many_series_one_param_dev(
data_tm_f32=data_tm_f32,
period=14,
predict=14,
bandwidth=1.0,
device_id=0,
) 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); CUDA Bindings (Rust)
use vector_ta::cuda::CudaVoss;
use vector_ta::indicators::voss::VossBatchRange;
let cuda = CudaVoss::new(0)?;
let data_f32: [f32] = /* ... */;
let sweep = VossBatchRange::default();
let out = cuda.voss_batch_dev(&data_f32, &sweep)?;
let _ = out; Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-08