Holt–Winters Moving Average (HWMA)

Parameters: na = 0.2 (0–1) | nb = 0.1 (0–1) | nc = 0.1 (0–1)

Overview

The Holt Winters Moving Average adapts to changing market dynamics by separately tracking three components of price movement: the current level, the trend direction, and the rate of acceleration, combining them into a sophisticated forecast that anticipates rather than follows price changes. Each component uses its own exponential smoothing factor, allowing traders to fine tune how quickly the indicator responds to changes in price level versus trend versus momentum acceleration. The level component (na) captures the baseline price, the trend component (nb) measures directional bias, and the acceleration component (nc) detects whether the trend strengthens or weakens, creating a moving average that not only smooths but also extrapolates. When markets accelerate, HWMA projects ahead of current price by incorporating the acceleration term, providing earlier signals than traditional moving averages that merely follow. During trend transitions, the separate tracking of components allows the indicator to distinguish between temporary pullbacks and genuine reversals by monitoring whether deceleration affects just the level or also the underlying trend. Advanced traders leverage HWMA's three dimensional analysis to optimize entries during accelerating trends and exits when deceleration begins, as the indicator's forward projection capability reveals momentum shifts before they become visible in price action alone.

Implementation Examples

Compute HWMA from a slice or candles:

use vectorta::indicators::hwma::{hwma, HwmaInput, HwmaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = HwmaParams { na: Some(0.2), nb: Some(0.1), nc: Some(0.1) };
let input = HwmaInput::from_slice(&prices, params);
let result = hwma(&input)?;

// Using with Candles (default: source="close", na=0.2, nb=0.1, nc=0.1)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = HwmaInput::with_default_candles(&candles);
let result = hwma(&input)?;

// Access values
for value in result.values {
    println!("HWMA: {}", value);
}

API Reference

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

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

// From candles with default params (source="close", na=0.2, nb=0.1, nc=0.1)
HwmaInput::with_default_candles(&Candles) -> HwmaInput
Parameters Structure
pub struct HwmaParams {
    pub na: Option<f64>, // Default: 0.2
    pub nb: Option<f64>, // Default: 0.1
    pub nc: Option<f64>, // Default: 0.1
}
Output Structure
pub struct HwmaOutput {
    pub values: Vec<f64>, // HWMA values
}
Validation, Warmup & NaNs
  • Errors on empty input (HwmaError::EmptyData) and when all values are NaN (HwmaError::AllValuesNaN).
  • Parameters must be finite and strictly in (0, 1); otherwise HwmaError::InvalidParams { na, nb, nc }.
  • Warmup: finds the first finite input index first; indices < first are NaN. The first finite output equals the first finite input.
  • No additional period‑based warmup. Output length matches input length.
  • hwma_with_kernel_into requires out.len() == input.len() or returns HwmaError::InvalidOutputBuffer. It writes only the warmup prefix to NaN and computes the rest directly.
  • Subsequent non‑finite inputs after warmup propagate through floating‑point arithmetic (no special gap handling in kernels).
  • Streaming: HwmaStream::try_new validates params; update returns a value from the first update onward.
Error Handling
use vectorta::indicators::hwma::{hwma, HwmaError};

match hwma(&input) {
    Ok(output) => process_results(output.values),
    Err(HwmaError::EmptyData) => println!("Input data is empty"),
    Err(HwmaError::AllValuesNaN) => println!("All input values are NaN"),
    Err(HwmaError::InvalidParams { na, nb, nc }) =>
        println!("Params must be in (0,1): na={}, nb={}, nc={}", na, nb, nc),
    Err(HwmaError::InvalidOutputBuffer { expected, actual }) =>
        println!("Output buffer size {}, expected {}", actual, expected),
}

Python Bindings

Basic Usage

Calculate HWMA using NumPy arrays (explicit parameters required):

import numpy as np
from vectorta import hwma

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5], dtype=float)

# Specify smoothing factors explicitly
values = hwma(prices, na=0.2, nb=0.1, nc=0.1, kernel="auto")
print(values)
Streaming Real-time Updates

O(1) update complexity with internal state:

from vectorta import HwmaStream

stream = HwmaStream(na=0.2, nb=0.1, nc=0.1)
for price in price_feed:
    y = stream.update(float(price))
    if y is not None:
        print(y)
Batch Parameter Optimization

Test multiple (na, nb, nc) combinations:

import numpy as np
from vectorta import hwma_batch

prices = np.asarray([/* your data */], dtype=float)

na_range = (0.1, 0.3, 0.1)
nb_range = (0.05, 0.2, 0.05)
nc_range = (0.05, 0.2, 0.05)

out = hwma_batch(prices, na_range=na_range, nb_range=nb_range, nc_range=nc_range, kernel="auto")
print(out["values"].shape)  # (num_combinations, len(prices))
print(out["na"], out["nb"], out["nc"])
CUDA Acceleration

Available when built with CUDA support:

import numpy as np
from vectorta import hwma_cuda_batch_dev, hwma_cuda_many_series_one_param_dev

prices = np.asarray([/* your data */], dtype=np.float64)
dev_out = hwma_cuda_batch_dev(prices, na_range=(0.1, 0.3, 0.1), nb_range=(0.05, 0.2, 0.05), nc_range=(0.05, 0.2, 0.05), device_id=0)

# Many series (time-major), one parameter set
data_tm = np.asarray([/* T x N */], dtype=np.float32)
dev_out2 = hwma_cuda_many_series_one_param_dev(data_tm, na=0.2, nb=0.1, nc=0.1, device_id=0)

JavaScript/WASM Bindings

Basic Usage

Calculate HWMA in JavaScript/TypeScript:

import { hwma_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);
const values = hwma_js(prices, 0.2, 0.1, 0.1);
console.log('HWMA values:', values);
Memory-Efficient Operations

Use zero-copy operations for better performance with large datasets:

import { hwma_alloc, hwma_free, hwma_into, memory } from 'vectorta-wasm';

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

// Allocate WASM memory for input and output
const inPtr = hwma_alloc(length);
const outPtr = hwma_alloc(length);

// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, length).set(prices);

// Calculate HWMA directly into allocated memory
// Args: in_ptr, out_ptr, len, na, nb, nc
hwma_into(inPtr, outPtr, length, 0.2, 0.1, 0.1);

// Read results from WASM memory (slice() to copy out)
const hwmaValues = new Float64Array(memory.buffer, outPtr, length).slice();

// Free allocated memory when done
hwma_free(inPtr, length);
hwma_free(outPtr, length);

console.log('HWMA values:', hwmaValues);
Batch Processing

Test multiple parameter combinations efficiently:

import { hwma_batch_js, hwma_batch_metadata_js } from 'vectorta-wasm';

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

// Define parameter sweep ranges
const naStart = 0.1, naEnd = 0.3, naStep = 0.1;
const nbStart = 0.05, nbEnd = 0.2, nbStep = 0.05;
const ncStart = 0.05, ncEnd = 0.2, ncStep = 0.05;

// Get metadata for parameter combinations: [na1, nb1, nc1, na2, nb2, nc2, ...]
const meta = hwma_batch_metadata_js(
  naStart, naEnd, naStep,
  nbStart, nbEnd, nbStep,
  ncStart, ncEnd, ncStep
);
const combos = meta.length / 3;

// Calculate all combinations (flat array)
const flat = hwma_batch_js(
  prices,
  naStart, naEnd, naStep,
  nbStart, nbEnd, nbStep,
  ncStart, ncEnd, ncStep
);

// Reshape flat results into rows
const rows: number = combos;
const cols: number = prices.length;
const matrix: number[][] = [];
for (let r = 0; r < rows; r++) {
  const start = r * cols;
  matrix.push(Array.from(flat.slice(start, start + cols)));
}

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators