Files
wickra/docs/wiki/indicators/trend/Indicator-Wma.md
T
kingchenc b3ddbea584 E14: remove hardcoded local paths from the docs
Indicators-Overview.md referenced the absolute author-machine paths
D:\Coding\Wickra\crates\... and D:\Coding\Wickra\bindings\... in its
"Source-of-truth files" section, and seven trend-indicator pages had
Node examples that did require('D:/Coding/Wickra/bindings/node').

Replace the overview paths with repo-relative GitHub links and change
the Node examples to require('wickra'), the published npm package name
a reader would actually use. No D:/Coding path remains anywhere in docs.
2026-05-22 16:18:48 +02:00

5.9 KiB

WMA

Weighted Moving Average with linear weights 1, 2, …, period, so the most recent bar carries the most weight.

Quick reference

Field Value
Family Trend
Sub-category Simple averages
Input type f64 (single close)
Output type f64
Output range unbounded; tracks the input price scale
Default parameters period is required (no default in either binding)
Warmup period period
Interpretation Front-weighted trend filter; faster than Sma, smoother than Ema.

Formula

weights = [1, 2, ..., n]                              // n = period
W       = n * (n + 1) / 2                             // sum of weights
WMA_t   = (1 / W) * Σ_{i=0}^{n-1} (n - i) * price_{t-i}
        = (1 / W) * (n * price_t + (n-1) * price_{t-1} + ... + 1 * price_{t-n+1})

Maintained in O(1) using the identity that, when sliding the window by one, every retained element's weight drops by exactly one and the newcomer enters at weight n:

new_weight_sum = old_weight_sum - old_value_sum + n * new_input
new_value_sum  = old_value_sum  - oldest_value  + new_input

This is the bookkeeping in the steady-state branch of update; during warmup the full Σ weight·value is computed once when the window first fills.

Parameters

Name Type Default Valid range Description
period usize none >= 1 Length of the rolling window. period = 0 errors with Error::PeriodZero. period = 1 is a pass-through.

(The Python class wickra.WMA(period) does not set a #[pyo3(signature)] default; pass the period explicitly.)

Inputs / Outputs

From crates/wickra-core/src/indicators/wma.rs:

impl Indicator for Wma {
    type Input = f64;
    type Output = f64;
    // update(&mut self, input: f64) -> Option<f64>
}

Python returns float | None from update and a numpy.ndarray (float64, NaN for warmup) from batch. Node returns number | null and Array<number> (with NaN placeholders) respectively.

Warmup

Wma::new(period).warmup_period() == period. Like Sma, the first emission lands on the period-th update() call: the window needs exactly period values for the weighted sum to be defined. There is no seeding step beyond filling the window.

Edge cases

  • Constant series. For [c; n], every element contributes c · weight_i and the result is c · ΣW / ΣW = c. The proptest proptest_matches_naive exercises this implicitly across many random inputs; the textbook period = 4 test confirms WMA(4) of [1, 2, 3, 4] is exactly (1·1 + 2·2 + 3·3 + 4·4) / 10 = 30 / 10 = 3.0.
  • NaN / infinity inputs. The first line of update is if !input.is_finite() { return self.value(); }. Non-finite inputs are silently dropped — they do not advance warmup, do not corrupt the rolling sums, and the previously emitted value (if any) is returned.
  • Reset. wma.reset() clears the window and both rolling sums; the next update starts a new warmup countdown.

Examples

Rust

use wickra::{BatchExt, Indicator, Wma};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut wma = Wma::new(4)?;
    let out: Vec<Option<f64>> = wma.batch(&[1.0, 2.0, 3.0, 4.0]);
    println!("{:?}", out);
    println!("warmup_period = {}", wma.warmup_period());
    Ok(())
}

Output:

[None, None, None, Some(3.0)]
warmup_period = 4

The fourth input emits (1·1 + 2·2 + 3·3 + 4·4) / (1+2+3+4) = 30 / 10 = 3.0. This matches the known_values_period_4 unit test in crates/wickra-core/src/indicators/wma.rs.

Python

import numpy as np
import wickra as ta

wma = ta.WMA(4)
print(wma.batch(np.array([1.0, 2.0, 3.0, 4.0])))
print("warmup_period =", wma.warmup_period())

Output:

[nan nan nan  3.]
warmup_period = 4

Node

const ta = require('wickra');
const wma = new ta.WMA(4);
console.log(wma.batch([1, 2, 3, 4]));
console.log('warmupPeriod:', wma.warmupPeriod());

Output:

[ NaN, NaN, NaN, 3 ]
warmupPeriod: 4

Interpretation

Wma sits between Sma and Ema on the lag/responsiveness spectrum: because the most recent bar carries weight n (vs 1 for the oldest), direction changes propagate faster than in Sma, but the smooth linear decay produces less of the "exponential tail" overshoot you sometimes see with Ema. The same two crossover signals (price-vs-WMA and fast-WMA-vs-slow-WMA) apply.

The most important downstream use of Wma inside Wickra is Hma: Hma is built entirely from three Wma instances (see Indicator-Hma.md).

Common pitfalls

  • Mistaking linear weights for exponential ones. A Wma(20) is not an Ema(20); the weights decay linearly (20, 19, 18, …, 1) rather than geometrically, so very old bars still contribute (weight 1) where in an EMA they would have decayed to near zero. If you want the exponential decay, use Ema.
  • Comparing Wma(period) to a "WMA" from a different library and finding the seed off. Wickra's Wma has no separate seeding step — it simply returns None until the window is full and then returns the exact weighted mean from input period onward. Some libraries pre-seed with a partial-window value; that is a different convention and will produce different first-few-bar values.

References

The linearly-weighted moving average is older than most named indicators and has no single canonical citation; TA-Lib's WMA is the standard reference implementation and matches Wickra's output bit-for-bit.

See also