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 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(). 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 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05