Zero Lag Exponential Moving Average (ZLEMA)

Parameters: 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 are NaN.

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 > 0 and period ≤ data.len(); otherwise ZlemaError::InvalidPeriod.
  • Requires at least period valid points after the first finite value; else ZlemaError::NotEnoughValidData.
  • Warmup index: warm = first_non_nan + period − 1. Indices before warm are NaN.
  • De-lagging engages only after sufficient history: for indices before first + lag, raw values feed EMA.
  • Streaming: leading NaNs delay the first value; subsequent NaNs 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

Comparison:
View:

Across sizes, Rust CPU runs about 1.32× faster than Tulip C in this benchmark.

Loading chart...

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

Related Indicators