MAC-Z VWAP (MACZ)
fast_length = 12 | slow_length = 25 | signal_length = 9 | lengthz = 20 | length_stdev = 25 | a = 1 (-2–2) | b = 1 (-2–2) | use_lag = | gamma = 0.02 Overview
MAC-Z VWAP combines the trend-following characteristics of MACD with the mean reversion properties of Z-score to create a composite momentum oscillator that derives signals from both volume and price action. The indicator calculates ZVWAP by taking the difference between price and VWAP divided by the standard deviation over a specified window, then combines this with MACD normalized by the population standard deviation to create a volatility-adjusted momentum reading. This dual approach leverages the counter-trend component of the Z-score to adjust and improve the trend component of MACD, resulting in more accurate signals that describe actual market behavior without assumptions. The optional Laguerre filter, developed by John Ehlers for signal processing applications, can smooth the MACZ values using fourth-order polynomials that emphasize recent data while minimizing noise and lag. Weighted coefficients a and b allow traders to adjust the relative influence of the ZVWAP mean reversion component versus the normalized MACD momentum component, enabling customization for different market conditions. The final output histogram represents MACZ minus its signal line SMA, oscillating around zero where positive values suggest bullish momentum and negative values indicate bearish pressure, with the indicator remaining comparable across different timeframes and securities due to its normalization approach.
Defaults: fast=12, slow=25, signal=9, lengthz=20, length_stdev=25, a=1.0, b=1.0, use_lag=false, gamma=0.02.
Implementation Examples
Compute MACZ from slices or candles:
use vectorta::indicators::macz::{macz, MaczInput, MaczParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Price slice
let prices = vec![100.0, 101.0, 102.5, 101.7, 103.2];
let input = MaczInput::from_slice(&prices, MaczParams::default());
let out = macz(&input)?;
// Candles (uses close + volume by default)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = MaczInput::with_default_candles(&candles); // source="close", defaults applied
let out = macz(&input)?;
// Access histogram values
for v in out.values { println!("MACZ hist: {}", v); } API Reference
Input Methods ▼
// From price slice
MaczInput::from_slice(&[f64], MaczParams) -> MaczInput
MaczInput::from_slice_with_volume(&[f64], &[f64], MaczParams) -> MaczInput
// From candles (uses provided source and volume)
MaczInput::from_candles(&Candles, &str, MaczParams) -> MaczInput
MaczInput::from_candles_with_volume(&Candles, &str, &[f64], MaczParams) -> MaczInput
// Defaults
MaczInput::with_default_candles(&Candles) -> MaczInput // source="close", defaults
MaczInput::with_default_slice(&[f64]) -> MaczInput
// Back-compat alias
MaczInput::with_default_candles_auto_volume(&Candles) -> MaczInput Parameters Structure ▼
pub struct MaczParams {
pub fast_length: Option<usize>, // Default: 12 (> 0)
pub slow_length: Option<usize>, // Default: 25 (> 0)
pub signal_length: Option<usize>, // Default: 9 (> 0)
pub lengthz: Option<usize>, // Default: 20 (> 0)
pub length_stdev: Option<usize>, // Default: 25 (> 0)
pub a: Option<f64>, // Default: 1.0 (−2.0..=2.0)
pub b: Option<f64>, // Default: 1.0 (−2.0..=2.0)
pub use_lag: Option<bool>, // Default: false
pub gamma: Option<f64>, // Default: 0.02 (0 ≤ gamma < 1)
} Output Structure ▼
pub struct MaczOutput {
pub values: Vec<f64>, // Histogram: MACZ − SMA(MACZ, signal_length)
} Validation, Warmup & NaNs ▼
fast_length,slow_length,signal_length,lengthz,length_stdevmust be> 0; otherwiseMaczError::InvalidPeriod.- Must have at least
max(fast, slow, lengthz, length_stdev)valid points after the first finite value; elseMaczError::NotEnoughValidData. aandbmust be within[−2.0, 2.0](inclusive) orMaczError::InvalidA/InvalidB.gammamust satisfy0 ≤ gamma < 1orMaczError::InvalidGamma.- Warmup: histogram starts at
warm_hist = first_non_nan + max(slow,lengthz,length_stdev) + signal_length − 1; indices before areNaN. - VWAP uses candle volume when provided; without volume, VWAP ≡
SMA(price, lengthz). Volume length must match price length. - Strict windows: any
NaNinside fast/slow/lengthz/length_stdev windows yieldsNaNfor that step. - Streaming:
update()returnsNoneduring warmup; can returnSome(NaN)until the signal window is fully populated.
Error Handling ▼
use vectorta::indicators::macz::{macz, MaczError};
match macz(&input) {
Ok(output) => consume(output.values),
Err(MaczError::EmptyInputData) => eprintln!("No data provided"),
Err(MaczError::AllValuesNaN) => eprintln!("All values are NaN"),
Err(MaczError::InvalidPeriod { period, data_len }) => {
eprintln!("Invalid period: {period}, data length: {data_len}");
}
Err(MaczError::NotEnoughValidData { needed, valid }) => {
eprintln!("Need {needed} valid points, have {valid}");
}
Err(MaczError::InvalidGamma { gamma }) => eprintln!("Invalid gamma: {gamma}"),
Err(MaczError::InvalidA { a }) => eprintln!("A out of range: {a}"),
Err(MaczError::InvalidB { b }) => eprintln!("B out of range: {b}"),
Err(MaczError::VolumeRequired) => eprintln!("Volume data required"),
Err(MaczError::InvalidParameter { msg }) => eprintln!("Invalid parameter: {msg}"),
} Python Bindings
Basic Usage ▼
Compute MACZ using NumPy arrays; volume is optional:
import numpy as np
from vectorta import macz
prices = np.array([100.0, 101.0, 102.5, 101.7, 103.2])
# Defaults (fast=12, slow=25, sig=9, lengthz=20, length_stdev=25, a=1.0, b=1.0, use_lag=False, gamma=0.02)
values = macz(prices)
# With parameters and kernel selection
values = macz(
prices,
fast_length=12,
slow_length=25,
signal_length=9,
lengthz=20,
length_stdev=25,
a=1.0,
b=1.0,
use_lag=False,
gamma=0.02,
kernel="auto", # or "scalar"
)
print(values) # NumPy array of histogram values Streaming Real‑time Updates ▼
from vectorta import MaczStream
stream = MaczStream(
fast_length=12, slow_length=25, signal_length=9,
lengthz=20, length_stdev=25, a=1.0, b=1.0,
use_lag=False, gamma=0.02,
)
for price, volume in feed():
hist = stream.update(price, volume)
if hist is not None:
process(hist) Batch Parameter Optimization ▼
import numpy as np
from vectorta import macz_batch
prices = np.array([...])
results = macz_batch(
prices,
fast_length_range=(8, 16, 2),
slow_length_range=(20, 30, 5),
signal_length_range=(5, 9, 2),
lengthz_range=(16, 24, 4),
length_stdev_range=(20, 30, 5),
a_range=(0.5, 1.5, 0.5),
b_range=(0.5, 1.5, 0.5),
use_lag_range=(False, False, False),
gamma_range=(0.02, 0.02, 0.0),
kernel="auto",
)
print(results.keys()) # values, fast_lengths, slow_lengths, signal_lengths CUDA Acceleration ▼
CUDA support for MACZ is currently under development.
# Coming soon: CUDA-accelerated MACZ calculations JavaScript/WASM Bindings
Basic Usage ▼
Calculate MACZ in JavaScript/TypeScript:
import { macz_js } from 'vectorta-wasm';
const prices = new Float64Array([/* your data */]);
const hist = macz_js(prices, 12, 25, 9, 20, 25, 1.0, 1.0, false, 0.02);
console.log('MACZ histogram:', hist); Memory‑Efficient Operations ▼
Use zero‑copy operations for large datasets:
import { macz_alloc, macz_free, macz_into, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* data */]);
const n = prices.length;
const inPtr = macz_alloc(n);
const outPtr = macz_alloc(n);
new Float64Array(memory.buffer, inPtr, n).set(prices);
// Args: in_ptr, out_ptr, len, fast, slow, sig, lengthz, length_stdev, a, b, use_lag, gamma
macz_into(inPtr, outPtr, n, 12, 25, 9, 20, 25, 1.0, 1.0, false, 0.02);
const hist = new Float64Array(memory.buffer, outPtr, n).slice();
macz_free(inPtr, n);
macz_free(outPtr, n); Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05