Files
wickra/docs/wiki/indicators/moving-averages/Indicator-Wma.md
T
kingchenc d2f99efd78 F13c: restructure the indicator catalogue into eight families
The original taxonomy was four classical families plus a statistics group,
with the F1-F12 expansion slotted in as sub-categories. This regroups the
whole 71-indicator catalogue into eight top-level families, each with at
least five members:

  Moving Averages (12), Momentum Oscillators (13), Trend & Directional (9),
  Price Oscillators (5), Volatility & Bands (12), Trailing Stops (5),
  Volume (9), Price Statistics (7).

- Wiki: docs/wiki/indicators/ reorganised into eight family folders; all 71
  indicator pages moved with `git mv`. Every internal cross-link is
  normalised to `../<family>/Indicator-X.md`, each page's `Family` field is
  set to its new family, and two pre-existing `../Indicator-Chaining.md`
  links (should have been `../../`) are corrected. A link check confirms
  every relative wiki link resolves.
- Indicators-Overview.md fully rewritten around the eight families;
  Home.md indicator reference and the README family table follow suit.
- Warmup-Periods.md gains the eight F13 indicators; CHANGELOG records the
  46-indicator expansion (25 -> 71) and the eight-family taxonomy.
- Tests: Node indicators.test.js and Python test_new_indicators.py cover
  all eight new indicators (Node 91/91, Python 117/117 green).

cargo fmt + clippy (core/wickra/data/wasm/node) clean; 508 core tests,
25 data tests and 74 doctests green.
2026-05-22 21:21:56 +02:00

186 lines
5.9 KiB
Markdown

# WMA
> Weighted Moving Average with linear weights `1, 2, …, period`, so the
> most recent bar carries the most weight.
## Quick reference
| Field | Value |
|-------|-------|
| Family | Moving 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`:
```rust
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
```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
```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
```javascript
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](../moving-averages/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
- [Indicator-Sma.md](../moving-averages/Indicator-Sma.md) — equal weights instead of linear.
- [Indicator-Ema.md](../moving-averages/Indicator-Ema.md) — exponential decay instead of linear.
- [Indicator-Hma.md](../moving-averages/Indicator-Hma.md) — Hull MA, built from three WMAs.
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.