Hull Moving Average (HMA)
period = 5 Overview
The Hull Moving Average eliminates nearly all lag from traditional moving averages through an ingenious mathematical technique that doubles the weight of recent prices, subtracts out the slower average, then smooths the result with a square root period weighted average. This three step process first calculates a fast WMA at half the period and doubles it, then subtracts a full period WMA to remove lag, finally smoothing this raw difference with a WMA of square root period length to restore stability without reintroducing delay. The result tracks price movements with exceptional responsiveness during trends while maintaining the smoothness needed to filter out market noise and prevent false signals. Traders particularly value HMA for catching trend changes at their inception rather than after significant moves have already occurred, as the indicator turns coincident with price rather than lagging behind like traditional averages. When HMA slopes upward, it confirms bullish momentum with minimal delay, while downward slopes provide early warning of bearish pressure before standard moving averages even begin to turn. The mathematical elegance of using the square root of the period for final smoothing creates an optimal balance between noise reduction and responsiveness that adapts naturally to different timeframes without requiring parameter adjustments.
Implementation Examples
Compute HMA from slices or candles:
use vectorta::indicators::moving_averages::hma::{hma, HmaInput, HmaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// From a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = HmaParams { period: Some(5) }; // default 5
let input = HmaInput::from_slice(&prices, params);
let result = hma(&input)?;
// From Candles with defaults (source="close", period=5)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = HmaInput::with_default_candles(&candles);
let result = hma(&input)?;
// Access values
for v in result.values { println!("HMA: {}", v); } API Reference
Input Methods ▼
// From price slice
HmaInput::from_slice(&[f64], HmaParams) -> HmaInput
// From candles with custom source
HmaInput::from_candles(&Candles, &str, HmaParams) -> HmaInput
// With defaults (source="close", period=5)
HmaInput::with_default_candles(&Candles) -> HmaInput Parameters Structure ▼
#[derive(Debug, Clone)]
pub struct HmaParams {
pub period: Option<usize>, // Default: 5
} Output Structure ▼
pub struct HmaOutput {
pub values: Vec<f64>, // HMA values (NaN prefix during warmup)
} Validation, Warmup & NaNs ▼
- Rejects empty input:
HmaError::NoData; rejects all-NaN inputs:HmaError::AllValuesNaN. period > 0andperiod ≤ data_len; elseHmaError::InvalidPeriod.- Requires at least
periodvalid points after the first finite value; elseHmaError::NotEnoughValidData. - Additional constraints:
period/2 > 0(HmaError::ZeroHalf) and⌊√period⌋ > 0(HmaError::ZeroSqrtPeriod). - First finite output index is
first + period + ⌊√period⌋ − 2; earlier indices areNaN.
Error Handling ▼
use vectorta::indicators::moving_averages::hma::{hma, HmaInput, HmaParams, HmaError};
let input = HmaInput::from_slice(&prices, HmaParams { period: Some(5) });
match hma(&input) {
Ok(out) => println!("HMA ok: {} values", out.values.len()),
Err(HmaError::NoData) => eprintln!("hma: no data provided"),
Err(HmaError::AllValuesNaN) => eprintln!("hma: all values are NaN"),
Err(HmaError::InvalidPeriod { period, data_len }) =>
eprintln!("hma: invalid period={} for len={}", period, data_len),
Err(HmaError::ZeroHalf { period }) =>
eprintln!("hma: half window is zero for period={}", period),
Err(HmaError::ZeroSqrtPeriod { period }) =>
eprintln!("hma: sqrt window is zero for period={}", period),
Err(HmaError::NotEnoughValidData { needed, valid }) =>
eprintln!("hma: need {} valid points, have {}", needed, valid),
} Python Bindings
Basic Usage ▼
Compute HMA with NumPy arrays (period is required):
import numpy as np
from vectorta import hma
prices = np.array([100.0, 101.0, 102.5, 101.8, 103.2], dtype=float)
# Required period; optional kernel: "auto" | "scalar" | "avx2" | "avx512"
vals = hma(prices, period=5, kernel="auto")
print(vals) Streaming Real-time Updates ▼
from vectorta import HmaStream
stream = HmaStream(period=5)
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 hma_batch
prices = np.array([...], dtype=float)
out = hma_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 hma_cuda_batch_dev, hma_cuda_many_series_one_param_dev
# Option 1: One series, many parameters (device buffer returned)
prices_f32 = np.array([...], dtype=np.float32)
period_range = (5, 30, 5)
device_buf, meta = hma_cuda_batch_dev(prices_f32, period_range, device_id=0)
print(meta['periods'])
# Option 2: Many series (time-major), one parameter
tm = np.array([...], dtype=np.float32) # shape [T, N]
dev_arr = hma_cuda_many_series_one_param_dev(tm, period=20, device_id=0) JavaScript/WASM Bindings
Basic Usage ▼
Calculate HMA in JavaScript/TypeScript:
import { hma_js } from 'vectorta-wasm';
const prices = new Float64Array([100, 101, 102.5, 101.8, 103.2]);
const values = hma_js(prices, 5);
console.log(values); Memory-Efficient Operations ▼
Use zero-copy operations for large datasets:
import { hma_alloc, hma_free, hma_into, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* your data */]);
const len = prices.length;
const inPtr = hma_alloc(len);
const outPtr = hma_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(prices);
// Args: in_ptr, out_ptr, len, period
hma_into(inPtr, outPtr, len, 5);
const values = new Float64Array(memory.buffer, outPtr, len).slice();
hma_free(inPtr, len);
hma_free(outPtr, len); Batch Processing ▼
Run many period values and get a row-major matrix:
import { hma_batch, hma_batch_into, hma_alloc, hma_free, hma_batch_js, hma_batch_metadata_js, 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 = hma_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 = hma_alloc(rows * cols);
const inPtr = hma_alloc(cols);
new Float64Array(memory.buffer, inPtr, cols).set(prices);
const computedRows = hma_batch_into(inPtr, outPtr, cols, start, end, step);
const flat = new Float64Array(memory.buffer, outPtr, rows * cols).slice();
hma_free(inPtr, cols);
hma_free(outPtr, rows * cols);
// Legacy helpers also exist
const meta = hma_batch_metadata_js(start, end, step);
const flatLegacy = hma_batch_js(prices, start, end, step); Performance Analysis
Across sizes, Rust CPU runs about 1.12× 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