Ehlers Hann Moving Average (EHMA)

Parameters: period = 14

Overview

The Ehlers Hann Moving Average (EHMA), developed by John Ehlers, applies a cosine based Hann window function to create a superior smoothing filter that reduces lag while maintaining excellent noise rejection. The Hann window, borrowed from digital signal processing, creates a bell shaped weighting curve where central values receive the highest weights and edge values taper smoothly to zero. This cosine weighting formula produces weights that follow a raised cosine pattern, peaking at the center of the window. Unlike simple or exponential averages that use fixed weighting schemes, the Hann window provides optimal frequency response characteristics, suppressing high frequency noise while preserving the underlying trend signal.

EHMA achieves superior smoothing compared to traditional moving averages while introducing less lag than comparable simple or weighted averages. The cosine based weights create a natural tapering that prevents the abrupt transitions found in simple moving averages, reducing overshooting and ringing effects. The 14 period default provides excellent balance between smoothness and responsiveness for most applications. Shorter periods create more responsive averages suitable for scalping, while longer periods produce smoother outputs ideal for position trading. The mathematical properties of the Hann window ensure that EHMA responds predictably to price changes without the irregular behavior sometimes seen in exotic moving averages.

Traders use EHMA as a refined alternative to traditional moving averages for trend identification and signal generation. Price crossovers with EHMA produce cleaner signals with fewer whipsaws than simple or exponential averages of the same period. The indicator excels as a baseline for other calculations, providing a smooth yet responsive foundation for oscillators, bands, or custom indicators. Many systematic traders prefer EHMA for its predictable mathematical properties and consistent performance across different market conditions. The reduced lag makes EHMA particularly effective as a trailing stop mechanism, staying closer to price during trends while still filtering out minor fluctuations that might trigger premature exits.

Implementation Examples

Compute EHMA from a slice or candles:

use vectorta::indicators::moving_averages::ehma::{ehma, EhmaInput, EhmaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From price slice
let prices = vec![100.0, 101.0, 102.5, 101.8, 103.2];
let params = EhmaParams { period: Some(14) }; // default is 14
let input = EhmaInput::from_slice(&prices, params);
let result = ehma(&input)?;

// From candles (defaults: period=14, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = EhmaInput::with_default_candles(&candles);
let result = ehma(&input)?;

for v in result.values { println!("EHMA: {}", v); }

API Reference

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

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

// With defaults (source="close", period=14)
EhmaInput::with_default_candles(&Candles) -> EhmaInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct EhmaParams {
    pub period: Option<usize>, // Default: 14
}
Output Structure
pub struct EhmaOutput {
    pub values: Vec<f64>, // EHMA values (leading NaNs during warmup)
}
Validation, Warmup & NaNs
  • period > 0; period ≤ data_len; otherwise EhmaError::InvalidPeriod.
  • Requires at least period valid points after first finite value; else EhmaError::NotEnoughValidData.
  • Leading outputs before first + period − 1 are NaN (warmup).
  • NaNs in the input window propagate to that output index.
Error Handling
use vectorta::indicators::moving_averages::ehma::{ehma, EhmaInput, EhmaParams, EhmaError};

let input = EhmaInput::from_slice(&prices, EhmaParams { period: Some(14) });
match ehma(&input) {
    Ok(out) => println!("EHMA ok: {} values", out.values.len()),
    Err(EhmaError::EmptyInputData) => eprintln!("ehma: input is empty"),
    Err(EhmaError::AllValuesNaN) => eprintln!("ehma: all inputs are NaN"),
    Err(EhmaError::InvalidPeriod { period, data_len }) =>
        eprintln!("ehma: invalid period={} for len={}", period, data_len),
    Err(EhmaError::NotEnoughValidData { needed, valid }) =>
        eprintln!("ehma: need {} valid points, have {}", needed, valid),
}

Python Bindings

Basic Usage

Compute EHMA with NumPy arrays (default period=14):

import numpy as np
from vectorta import ehma

prices = np.array([100.0, 101.0, 102.5, 101.8, 103.2])

# Default
vals = ehma(prices)

# Custom period and kernel selection
vals = ehma(prices, period=20, kernel="auto")
print(vals)
Streaming Real-time Updates
from vectorta import EhmaStream

stream = EhmaStream(period=14)
for price in price_feed:
    y = stream.update(price)
    if y is not None:
        handle(y)
Batch Parameter Sweep
import numpy as np
from vectorta import ehma_batch

prices = np.array([...], dtype=float)
out = ehma_batch(prices, period_range=(5, 30, 5), kernel="auto")

print(out['values'].shape)  # (rows, len(prices))
print(out['periods'])       # tested periods
CUDA Acceleration

Available when built with CUDA features.

import numpy as np
from vectorta import ehma_cuda_batch_dev, ehma_cuda_many_series_one_param_dev

# Option 1: One series, many parameters (device-resident output)
prices_f64 = np.array([...], dtype=float)
dev_arr = ehma_cuda_batch_dev(prices_f64, period_range=(5, 30, 1), device_id=0)

# Option 2: Many series (time-major), one parameter
data_tm_f32 = np.array([...], dtype=np.float32)  # shape [T, N]
dev_arr2 = ehma_cuda_many_series_one_param_dev(data_tm_f32, period=14, device_id=0)

JavaScript/WASM Bindings

Basic Usage

Calculate EHMA in JavaScript/TypeScript:

import { ehma_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 101, 102.5, 101.8, 103.2]);
const values = ehma_js(prices, 14);
console.log(values);
Memory-Efficient Operations
import { ehma_alloc, ehma_free, ehma_into, memory } from 'vectorta-wasm';

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

const inPtr = ehma_alloc(len);
const outPtr = ehma_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(prices);

// Args: in_ptr, out_ptr, len, period
ehma_into(inPtr, outPtr, len, 14);
const values = new Float64Array(memory.buffer, outPtr, len).slice();

ehma_free(inPtr, len);
ehma_free(outPtr, len);
Batch Processing

Run many period values and get a row-major matrix:

import { ehma_batch, ehma_batch_into, ehma_alloc, ehma_free, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);

// Unified helper (returns { values, combos, rows, cols })
const cfg = { period_range: [5, 30, 5] };
const res = ehma_batch(prices, cfg);

// Or zero-copy variant allocating output as rows*cols
const [start, end, step] = [5, 30, 5];
const rows = Math.floor((end - start) / step) + 1;
const cols = prices.length;
const outPtr = ehma_alloc(rows * cols);
const inPtr = ehma_alloc(cols);
new Float64Array(memory.buffer, inPtr, cols).set(prices);

const computedRows = ehma_batch_into(inPtr, outPtr, cols, start, end, step);
const flat = new Float64Array(memory.buffer, outPtr, rows * cols).slice();

ehma_free(inPtr, cols);
ehma_free(outPtr, rows * cols);

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators