Zero Lag Exponential Moving Average (ZLEMA)
period = 14 Overview
The Zero Lag Exponential Moving Average (ZLEMA) tackles the inherent lag of exponential smoothing by preprocessing the input data to compensate for delay before applying the EMA calculation. The algorithm first calculates a lag value as half the period length, then constructs a de-lagged price series using the formula 2×current_price - lagged_price. This de-lagging step effectively extrapolates the current trend forward, anticipating where price would be without smoothing delay. An exponential moving average then processes this adjusted series using standard EMA mechanics with the specified period. The combination of lag compensation and exponential smoothing produces a line that tracks price movements more closely than traditional EMAs while maintaining smooth behavior. Traders employ ZLEMA when they need the noise reduction benefits of exponential averaging but cannot tolerate the timing delays that typically accompany smoothing operations. The indicator proves particularly valuable for entry and exit timing in trending markets where even small delays can significantly impact trade outcomes. The default 14-period setting balances responsiveness with stability, though aggressive traders often shorten the period while longer settings suit position trading approaches.
Formula: lag = ⌊(n − 1)/2⌋, x't = 2·xt − xt−lag,
EMAt = α·x't + (1 − α)·EMAt−1, with α = 2/(n+1).
Defaults (VectorTA): period = 14.
See also
Interpretation & Use
- Trend filter: Price crossing above/below ZLEMA can signal momentum shifts earlier than EMA.
- Crossovers: Short/long ZLEMA crossovers generate responsive trend signals.
- Lag handling: De-lagging (
2·xt − xt−lag) may overshoot compared to EMA; consider risk controls in choppy markets. - Warmup: Values start after
first_non_nan + period − 1; earlier indices areNaN.
Implementation Examples
Basic usage from a slice or from candles (default period=14):
use vectorta::indicators::zlema::{zlema, ZlemaInput, ZlemaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with price data slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = ZlemaParams { period: Some(14) };
let input = ZlemaInput::from_slice(&prices, params);
let result = zlema(&input)?;
// Using with Candles data structure (defaults: period=14, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = ZlemaInput::with_default_candles(&candles);
let result = zlema(&input)?;
// Access the ZLEMA values
for value in result.values {
println!("ZLEMA: {}", value);
} API Reference
Input Methods ▼
// From price slice
ZlemaInput::from_slice(&[f64], ZlemaParams) -> ZlemaInput
// From candles with custom source
ZlemaInput::from_candles(&Candles, &str, ZlemaParams) -> ZlemaInput
// From candles with default params (close prices, period=14)
ZlemaInput::with_default_candles(&Candles) -> ZlemaInput Parameters Structure ▼
pub struct ZlemaParams {
pub period: Option<usize>, // Default: 14
} Output Structure ▼
pub struct ZlemaOutput {
pub values: Vec<f64>, // ZLEMA values
} Validation, Warmup & NaNs ▼
period > 0andperiod ≤ data.len(); otherwiseZlemaError::InvalidPeriod.- Requires at least
periodvalid points after the first finite value; elseZlemaError::NotEnoughValidData. - Warmup index:
warm = first_non_nan + period − 1. Indices beforewarmareNaN. - De-lagging engages only after sufficient history: for indices before
first + lag, raw values feed EMA. - Streaming: leading
NaNs delay the first value; subsequentNaNs taint the EMA state and propagate.
Error Handling ▼
use vectorta::indicators::zlema::ZlemaError;
match zlema(&input) {
Ok(output) => process_results(output.values),
Err(ZlemaError::EmptyInputData) =>
println!("Input data is empty"),
Err(ZlemaError::AllValuesNaN) =>
println!("All input values are NaN"),
Err(ZlemaError::InvalidPeriod { period, data_len }) =>
println!("Invalid period {} for length {}", period, data_len),
Err(ZlemaError::NotEnoughValidData { needed, valid }) =>
println!("Need {} valid points, only {}", needed, valid),
} Python Bindings
Basic Usage ▼
Calculate ZLEMA using NumPy arrays (default period=14):
import numpy as np
from vectorta import zlema
# Prepare price data as NumPy array
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])
# Calculate ZLEMA with default period (14)
result = zlema(prices, period=14)
# Or specify a custom period and kernel
result = zlema(prices, period=21, kernel="auto") Streaming ▼
import numpy as np
from vectorta import ZlemaStream
stream = ZlemaStream(14)
for price in [100.0, 101.0, np.nan, 102.0]:
value = stream.update(price)
if value is not None:
handle_value(value) Batch Optimization ▼
Test multiple period values in one pass:
import numpy as np
from vectorta import zlema_batch
prices = np.array([...])
period_range = (10, 30, 2) # 10, 12, 14, ..., 30
results = zlema_batch(
prices,
period_range=period_range,
kernel="auto"
)
print(results['values'].shape) # (num_periods, len(prices))
print(results['periods']) # list of tested periods CUDA Acceleration ▼
CUDA-accelerated ZLEMA is available when built with Python+CUDA features.
# Requires vectorta built with CUDA support
import numpy as np
from vectorta import zlema_cuda_batch_dev
prices_f32 = np.asarray([...], dtype=np.float32)
period_range = (10, 30, 2)
device_array, meta = zlema_cuda_batch_dev(
prices_f32,
period_range,
device_id=0
)
print(meta['periods'])
# device_array contains GPU-resident results (device-specific wrapper) JavaScript/WASM Bindings
Basic Usage ▼
Calculate ZLEMA in JavaScript/TypeScript:
import { zlema_js } from 'vectorta-wasm';
const prices = new Float64Array([100, 102, 101.5, 103, 105, 104.5]);
const result = zlema_js(prices, 14);
console.log('ZLEMA values:', result); Memory-Efficient Operations ▼
Use zero-copy operations for large datasets:
import { zlema_alloc, zlema_free, zlema_into, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* your data */]);
const length = prices.length;
const inPtr = zlema_alloc(length);
const outPtr = zlema_alloc(length);
new Float64Array(memory.buffer, inPtr, length).set(prices);
// Args: in_ptr, out_ptr, len, period
zlema_into(inPtr, outPtr, length, 14);
const zlemaValues = new Float64Array(memory.buffer, outPtr, length).slice();
zlema_free(inPtr, length);
zlema_free(outPtr, length);
console.log('ZLEMA values:', zlemaValues); Batch Processing ▼
Test multiple period values efficiently:
import { zlema_batch_js, zlema_batch_metadata_js } from 'vectorta-wasm';
const prices = new Float64Array([/* historical prices */]);
const start = 10, end = 30, step = 2; // 10, 12, ..., 30
const metadata = zlema_batch_metadata_js(start, end, step);
const numCombos = metadata.length; // periods only
const results = zlema_batch_js(prices, start, end, step);
// Flattened: [combo1..., combo2..., ...]; each combo is one period's ZLEMA series
const matrix = [] as Float64Array[];
for (let i = 0; i < numCombos; i++) {
const begin = i * prices.length;
const endIdx = begin + prices.length;
matrix.push(results.slice(begin, endIdx));
}
// Access specific period results
const period = metadata[0];
const values = matrix[0]; Export Code
use vectorta::indicators::zlema;
// Calculate ZLEMA with custom parameters
let result = zlema(&data, 14);
// Or using the builder pattern
let result = indicators::zlema::new()
.period(14)
.calculate(&data);This code snippet shows how to use the ZLEMA indicator with your current parameter settings.
Performance Analysis
Across sizes, Rust CPU runs about 1.32× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Arnaud Legoux Moving Average
Moving average indicator
Compound Ratio Moving Average (CoRa Wave)
Moving average indicator
Centered Weighted Moving Average
Moving average indicator
Double Exponential Moving Average
Moving average indicator
Ehlers Distance Coefficient Filter
Moving average indicator
Ehlers Error-Correcting EMA (ECEMA)
Moving average indicator