Files
wickra/docs/wiki/indicators/moving-averages/Indicator-Ema.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

201 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EMA
> Exponential Moving Average with smoothing factor `α = 2 / (period + 1)`,
> seeded from the SMA of the first `period` inputs (the TA-Lib convention).
## 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; or `Ema::with_alpha(α)` for a custom smoothing factor |
| Warmup period | `period` |
| Interpretation | Smoother, less laggy than `Sma` of the same length. |
## Formula
For `t >= period` (after warmup):
```
α = 2 / (period + 1)
seed = (1 / period) * Σ_{i=0}^{period-1} price_i // SMA of first `period` inputs
EMA_t = α * price_t + (1 - α) * EMA_{t-1} // recursive update
```
The first emitted value (at input `period`) is the seed itself, identical
to `Sma::new(period)` on the same prefix. From input `period + 1` onward
the recursive formula takes over. (`Ema::with_alpha(α)` skips the seed and
uses the very first input as the initial state, so `warmup_period() == 1`
in that mode — see the `with_alpha` method for details.)
Wilder's smoothing (used by `Rsi`/`Atr`/`Adx`) uses `α = 1/period`
instead; that is a different smoothing constant and a different
indicator family.
## Parameters
| Name | Type | Default | Valid range | Description |
|----------|----------|---------|-------------|-------------|
| `period` | `usize` | none | `>= 1` | Window length used to derive `α`. `period = 0` errors with `Error::PeriodZero`. |
| `α` (alternative constructor `Ema::with_alpha`) | `f64` | none | `(0.0, 1.0]` and finite | Custom smoothing factor; bypasses the period-derived α. Reported `period` is 1, `warmup_period() == 1`. Invalid `α` errors with `Error::InvalidPeriod`. |
(The Python class `wickra.EMA(period)` does not set a `#[pyo3(signature)]`
default; the period must be passed explicitly. `with_alpha` is Rust-only.)
## Inputs / Outputs
From `crates/wickra-core/src/indicators/ema.rs`:
```rust
impl Indicator for Ema {
type Input = f64;
type Output = f64;
// update(&mut self, input: f64) -> Option<f64>
}
```
Python streams as `float | None`, batches as a 1-D `numpy.ndarray`
(`NaN` for warmup). Node streams as `number | null`, batches as
`Array<number>` with `NaN` placeholders.
## Warmup
`Ema::new(period).warmup_period() == period`. The first non-empty value
is the SMA of the first `period` inputs (the "seed"); from there each
new input contributes `α * input + (1 α) * previous`. The unit test
`warmup_returns_none_until_seed` and the test
`first_value_equals_sma_seed` pin this contract.
This is the same warmup count as `Sma::new(period)` because the seed
itself is an SMA — `Ema` is "no slower to start emitting than `Sma`, just
more reactive afterwards".
## Edge cases
- **Constant series.** Feeding `[42.0; n]` returns `Some(42.0)` from input
`period` onward; the seed is `42.0`, and `α · 42 + (1 α) · 42 = 42`.
The unit test `constant_series_converges_to_constant` pins this.
- **NaN / infinity inputs.** The first line of `update` is
`if !input.is_finite() { return self.state; }`. Non-finite inputs are
silently dropped: they do not advance warmup, do not corrupt the
state, and the previously emitted value (if any) is returned. The unit
test `ignores_non_finite_input` pins this.
- **Reset.** `ema.reset()` clears both the smoothed state and the warmup
buffer; the next `update` starts a new warmup countdown.
## Examples
### Rust
```rust
use wickra::{BatchExt, Ema, Indicator};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut ema = Ema::new(3)?;
let out: Vec<Option<f64>> = ema.batch(&[1.0, 2.0, 3.0, 10.0]);
println!("{:?}", out);
println!("alpha = {}", ema.alpha());
Ok(())
}
```
Output:
```
[None, None, Some(2.0), Some(6.0)]
alpha = 0.5
```
`period = 3` gives `α = 2 / 4 = 0.5`. The seed at input 3 is the SMA of
`[1, 2, 3] = 2.0`; the next step is `0.5 · 10 + 0.5 · 2 = 6.0`. This
matches the `step_after_seed_uses_alpha_formula` unit test.
### Python
```python
import wickra as ta
ema = ta.EMA(3)
for x in [1.0, 2.0, 3.0, 10.0]:
print(x, '->', ema.update(x))
print('alpha:', ema.alpha)
print('warmup_period:', ema.warmup_period())
```
Output:
```
1.0 -> None
2.0 -> None
3.0 -> 2.0
10.0 -> 6.0
alpha: 0.5
warmup_period: 3
```
### Node
```javascript
const ta = require('wickra');
const ema = new ta.EMA(3);
for (const x of [1, 2, 3, 10]) {
console.log(x, '->', ema.update(x));
}
```
Output:
```
1 -> null
2 -> null
3 -> 2
10 -> 6
```
## Interpretation
`Ema` is the "default" smoothed trend filter for most practitioners. The
two main signals are price-vs-EMA and EMA-fast-vs-EMA-slow crossovers
(the latter is the basis of `MacdIndicator`). Compared with `Sma` at the
same period, `Ema` reacts faster to direction changes at the cost of
slightly noisier output — useful when you care about the inflection
point, not the long-run level.
Prefer `Ema` over `Sma` when you want a single-line trend filter with
moderate lag. Prefer `Dema` / `Tema` when the EMA lag is too much for
your timeframe. Prefer `Hma` when you want lag reduction *and* a built-in
noise filter (a triple WMA chain rather than a triple EMA chain).
## Common pitfalls
- **Confusing `α = 2/(n+1)` with Wilder's `α = 1/n`.** Wickra's `Ema`
uses the TA-Lib convention `α = 2/(n+1)`. The same numerical period
passed to `Rsi(14)` or `Atr(14)` uses `α = 1/14 ≈ 0.0714`, not
`α = 2/15 ≈ 0.1333`. They are different smoothing schemes; comparing
an EMA(14) line directly to the RSI/ATR's internal smoothing will not
match. If you want a Wilder-style EMA, build it on top of `Ema` with
the custom factor: `Ema::with_alpha(1.0 / 14.0)`.
- **Assuming the first emitted EMA is "the EMA".** The first value is
the SMA seed, not a recursively-smoothed EMA. The series only starts
behaving like an EMA from input `period + 1` onward. For short series,
this means the first emission tracks `Sma::new(period)` exactly — that
is the intended behaviour, not a bug.
## References
The TA-Lib seeding convention used here ("EMA is seeded with an SMA")
is documented in the TA-Lib source and replicated by virtually every
commercial charting platform. The recursive form
`EMA_t = α · price + (1 α) · EMA_{t-1}` is the standard exponential
smoothing identity attributed to Robert Brown (1956).
## See also
- [Indicator-Sma.md](../moving-averages/Indicator-Sma.md) — equal weights, identical seed.
- [Indicator-Dema.md](../moving-averages/Indicator-Dema.md) — `2·EMA EMA(EMA)`.
- [Indicator-Tema.md](../moving-averages/Indicator-Tema.md) — `3·EMA 3·EMA(EMA) + EMA(EMA(EMA))`.
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.