Z-Score

Parameters: period = 14 | ma_type = sma | nbdev = 1 | devtype = 0

Overview

The Z-Score normalizes price data by measuring how many standard deviations the current value sits from a rolling mean, creating a standardized oscillator useful for mean reversion strategies and outlier detection. The indicator calculates a moving average baseline using the specified method (SMA, EMA, or others), then measures dispersion through standard deviation, mean absolute deviation, or median absolute deviation. Dividing the difference between current price and the mean by the chosen deviation metric yields the Z-Score, typically ranging between -3 and +3 for normal market behavior. Values beyond ±2 suggest price has moved to statistical extremes relative to recent history, often signaling potential reversion opportunities. The nbdev parameter scales the deviation divisor, allowing traders to adjust sensitivity without changing the underlying period. This normalization makes Z-Score particularly valuable for comparing overbought and oversold conditions across different instruments or timeframes, as the standardized scale remains consistent regardless of absolute price levels. Mean reversion traders watch for extreme Z-Score readings to identify entry points, while trend followers may use sustained elevated readings to confirm strength. The flexible deviation type selection accommodates different market assumptions about return distributions and outlier handling.

Formula: zt = (xt − MAt) / Dt, where MA is the chosen moving average and D is the chosen deviation type (stddev/MAD/MedAD). Defaults (VectorTA): period=14, ma_type="sma", nbdev=1.0, devtype=0.

See also

Interpretation & Use

  • Zero-line: Above 0 = price above mean; below 0 = price below mean.
  • Extremes: |Z| ≥ 2 often marks statistically unusual moves; mean-reversion traders watch for fades.
  • Normalization: Compare standardized moves across symbols or regimes.
  • Choice of MA/Deviation: ma_type tunes the mean; devtype selects stddev/MAD/MedAD robustness.

Implementation Examples

Compute Z‑Score from a slice or candles:

use vectorta::indicators::zscore::{zscore, ZscoreInput, ZscoreParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0];
let params = ZscoreParams {
    period: Some(14),
    ma_type: Some("sma".to_string()),
    nbdev: Some(1.0),
    devtype: Some(0),
};
let input = ZscoreInput::from_slice(&prices, params);
let result = zscore(&input)?;

// From candles with defaults (period=14, ma_type="sma", nbdev=1.0, devtype=0; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = ZscoreInput::with_default_candles(&candles);
let result = zscore(&input)?;

// Access Z-Score values
for v in result.values {
    println!("zscore: {}", v);
}

API Reference

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

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

// From candles with default params (close, period=14, ma_type="sma", nbdev=1.0, devtype=0)
ZscoreInput::with_default_candles(&Candles) -> ZscoreInput
Parameters Structure
pub struct ZscoreParams {
    pub period: Option<usize>,   // Default: 14
    pub ma_type: Option<String>, // Default: "sma"
    pub nbdev: Option<f64>,      // Default: 1.0
    pub devtype: Option<usize>,  // Default: 0 (stddev)
}
Output Structure
pub struct ZscoreOutput {
    pub values: Vec<f64>, // Z-Score values
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; otherwise ZscoreError::InvalidPeriod.
  • Requires at least period valid points after the first finite value; otherwise ZscoreError::NotEnoughValidData.
  • Warmup: indices before first + period − 1 are NaN; first finite output at that index.
  • If the deviation at an index is 0.0 or NaN, the output is NaN for that index.
Error Handling
use vectorta::indicators::zscore::{zscore, ZscoreError};

match zscore(&input) {
    Ok(output) => process(output.values),
    Err(ZscoreError::AllValuesNaN) => eprintln!("All input values are NaN"),
    Err(ZscoreError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for data length {}", period, data_len),
    Err(ZscoreError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points, only {}", needed, valid),
    Err(ZscoreError::DevError(e)) => eprintln!("Deviation error: {}", e),
    Err(ZscoreError::MaError(e)) => eprintln!("MA error: {}", e),
}

Python Bindings

Basic Usage

Function zscore(data, period=14, ma_type="sma", nbdev=1.0, devtype=0, kernel=None) -> np.ndarray

import numpy as np
# from vectorta import zscore  # Depending on package exposure

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0], dtype=float)
zs = zscore(prices, period=14, ma_type='sma', nbdev=1.0, devtype=0)
print(zs)
Streaming
# from vectorta import ZscoreStream

stream = ZscoreStream(period=14, ma_type='sma', nbdev=1.0, devtype=0)
for price in [100.0, 101.0, 102.0, 101.5, 103.0]:
    val = stream.update(price)
    if val is not None:
        print('zscore:', val)
Batch Sweep
# from vectorta import zscore_batch
import numpy as np

prices = np.array([/* your data */], dtype=float)
out = zscore_batch(
    prices,
    period_range=(10, 20, 5),
    ma_type='sma',
    nbdev_range=(0.5, 2.0, 0.5),
    devtype_range=(0, 0, 0)
)

# out is a dict with 'values' shaped (rows, cols) and combo metadata
values = out['values']
periods = out['periods']
ma_types = out['ma_types']
nbdevs = out['nbdevs']
devtypes = out['devtypes']
CUDA

CUDA batch bindings available behind the cuda feature: zscore_cuda_batch_dev(data_f32, period_range, nbdev_range=(1.0,1.0,0.0), device_id=0).

JavaScript / WASM

Basic
import { zscore_js } from 'vectorta-wasm';

const prices = new Float64Array([/* data */]);
// Args: data, period, ma_type, nbdev, devtype
const zs = zscore_js(prices, 14, 'sma', 1.0, 0);
Memory-Efficient Operations
import { zscore_alloc, zscore_free, zscore_into, memory } from 'vectorta-wasm';

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

const inPtr = zscore_alloc(n);
const outPtr = zscore_alloc(n);
new Float64Array(memory.buffer, inPtr, n).set(prices);

// in_ptr, out_ptr, len, period, ma_type, nbdev, devtype
zscore_into(inPtr, outPtr, n, 14, 'sma', 1.0, 0);
const out = new Float64Array(memory.buffer, outPtr, n).slice();

zscore_free(inPtr, n);
zscore_free(outPtr, n);
Batch Processing
import { zscore_batch as zscore_batch_js } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);
const config = {
  period_range: [10, 20, 5],
  ma_type: 'sma',
  nbdev_range: [0.5, 2.0, 0.5],
  devtype_range: [0, 0, 0],
};

// Returns { values: f64[], combos: ZscoreParams[], rows, cols }
const res = zscore_batch_js(prices, config);
// Flattened row-major values: reshape using rows/cols as needed

Export Code

use vectorta::indicators::zscore;

// Calculate ZSCORE with custom parameters
let result = zscore(&data, 14, sma, 1, 0);

// Or using the builder pattern
let result = indicators::zscore::new()
    .period(14)
    .ma_type(sma)
    .nbdev(1)
    .devtype(0)
    .calculate(&data);

This code snippet shows how to use the ZSCORE indicator with your current parameter settings.

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators