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.
184 lines
6.0 KiB
Markdown
184 lines
6.0 KiB
Markdown
# SMA
|
||
|
||
> Simple Moving Average — the equal-weighted rolling mean of the last
|
||
> `period` closes, maintained as an O(1) rolling-sum state machine.
|
||
|
||
## 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 | Smoothed price level; price-vs-SMA crossings flag direction changes. |
|
||
|
||
## Formula
|
||
|
||
```
|
||
SMA_t = (1 / n) * Σ_{i=0}^{n-1} price_{t-i}
|
||
```
|
||
|
||
where `n = period`. Maintained incrementally as `sum -= window.pop_front();
|
||
sum += new_price; out = sum / n`, so `update` is O(1) regardless of
|
||
`period`.
|
||
|
||
## 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. |
|
||
|
||
(There is no Python `#[pyo3(signature = …)]` default for `SMA`, so
|
||
`wickra.SMA(period)` requires the period explicitly.)
|
||
|
||
## Inputs / Outputs
|
||
|
||
From `crates/wickra-core/src/indicators/sma.rs`:
|
||
|
||
```rust
|
||
impl Indicator for Sma {
|
||
type Input = f64;
|
||
type Output = f64;
|
||
// update(&mut self, input: f64) -> Option<f64>
|
||
}
|
||
```
|
||
|
||
A single `f64` close in, an `Option<f64>` out. The Python binding maps
|
||
this to `float | None` (streaming) or a `numpy.ndarray` of dtype
|
||
`float64` with `NaN` for warmup rows (batch). The Node binding maps it to
|
||
`number | null` / `Array<number>` with `NaN` for warmup.
|
||
|
||
## Warmup
|
||
|
||
`Sma::new(period).warmup_period() == period`. The first non-empty value
|
||
is emitted on the `period`-th `update()` call, because the window needs to
|
||
hold exactly `period` values before the mean is defined. There is no
|
||
seeding step beyond filling the window — `Sma` only ever stores its
|
||
running sum and the `VecDeque` of values, so its readiness condition is
|
||
literally `window.len() == period`.
|
||
|
||
## Edge cases
|
||
|
||
- **Constant series.** Feeding `[7.0; n]` returns `Some(7.0)` from input
|
||
`period` onward; the running-sum bookkeeping is exact for constants
|
||
(the unit test `constant_series_yields_constant_sma` pins this).
|
||
- **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 the window, do not corrupt
|
||
the sum, and the previous valid value (if any) is returned. The unit
|
||
test `ignores_non_finite_input_but_keeps_state` pins this behaviour.
|
||
- **Reset.** `sma.reset()` clears the window and the sum, returning the
|
||
indicator to a fresh `is_ready() == false` state. The next `update`
|
||
starts a new warmup countdown.
|
||
|
||
## Examples
|
||
|
||
### Rust
|
||
|
||
```rust
|
||
use wickra::{BatchExt, Indicator, Sma};
|
||
|
||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||
let mut sma = Sma::new(3)?;
|
||
let out: Vec<Option<f64>> = sma.batch(&[2.0, 4.0, 6.0, 8.0, 10.0]);
|
||
println!("{:?}", out);
|
||
println!("warmup_period = {}", sma.warmup_period());
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
Output:
|
||
|
||
```
|
||
[None, None, Some(4.0), Some(6.0), Some(8.0)]
|
||
warmup_period = 3
|
||
```
|
||
|
||
The first two inputs return `None` while the window fills; the third
|
||
emits `(2 + 4 + 6) / 3 = 4.0` and every subsequent input slides the
|
||
window by one. This matches the `known_reference_values` test in
|
||
`crates/wickra-core/src/indicators/sma.rs`.
|
||
|
||
### Python
|
||
|
||
```python
|
||
import numpy as np
|
||
import wickra as ta
|
||
|
||
sma = ta.SMA(3)
|
||
print(sma.batch(np.array([2.0, 4.0, 6.0, 8.0, 10.0])))
|
||
print("warmup_period =", sma.warmup_period())
|
||
```
|
||
|
||
Output:
|
||
|
||
```
|
||
[nan nan 4. 6. 8.]
|
||
warmup_period = 3
|
||
```
|
||
|
||
Warmup rows come back as `NaN` so the result aligns 1:1 with the input
|
||
array.
|
||
|
||
### Node
|
||
|
||
```javascript
|
||
const ta = require('wickra');
|
||
const sma = new ta.SMA(3);
|
||
console.log(sma.batch([2, 4, 6, 8, 10]));
|
||
console.log('warmupPeriod:', sma.warmupPeriod());
|
||
```
|
||
|
||
Output:
|
||
|
||
```
|
||
[ NaN, NaN, 4, 6, 8 ]
|
||
warmupPeriod: 3
|
||
```
|
||
|
||
## Interpretation
|
||
|
||
`Sma` is a smoothed price level. The two canonical signals are:
|
||
|
||
1. **Price–SMA crossover.** Close above the SMA suggests an uptrend, close
|
||
below suggests a downtrend. The longer the SMA, the slower (and more
|
||
trustworthy) the signal.
|
||
2. **Two-SMA crossover.** A fast SMA crossing above a slow SMA is the
|
||
classic "golden cross"; below is the "death cross". Either of `Ema`
|
||
or `Hma` will give earlier (but noisier) signals at the same period.
|
||
|
||
Prefer `Sma` when you want the simplest possible reference price — for
|
||
example, as the middle band of [`BollingerBands`](../../Indicators-Overview.md),
|
||
which uses an SMA by construction. Prefer `Ema` if you want the same
|
||
smoothness profile but slightly less lag on direction changes.
|
||
|
||
## Common pitfalls
|
||
|
||
- **Treating `period = 0` as "use a default".** `Sma::new(0)` returns
|
||
`Err(Error::PeriodZero)` in Rust and a `ValueError` in Python; there is
|
||
no implicit default. Pass an explicit period.
|
||
- **Slicing batch results with `> warmup_period` instead of
|
||
`~np.isnan(...)`.** In Python the batch output has `NaN` for warmup
|
||
rows; in Rust it has `None`. Use the warmup-aware mask to filter — see
|
||
the [Quickstart: Python](../../Quickstart-Python.md#macd-a-multi-column-indicator-and-its-warmup-nans)
|
||
pattern. Slicing by `prices.size - warmup_period` works for a single
|
||
indicator but breaks the moment you compose two of them via `Chain`.
|
||
|
||
## References
|
||
|
||
The simple moving average predates technical analysis as a discipline.
|
||
The implementation here follows the standard "rolling sum, slide on each
|
||
update" formulation; the matching reference implementations are TA-Lib
|
||
and pandas (`rolling(period).mean()`).
|
||
|
||
## See also
|
||
|
||
- [Indicator-Ema.md](../moving-averages/Indicator-Ema.md) — same smoothness budget, less lag.
|
||
- [Indicator-Wma.md](../moving-averages/Indicator-Wma.md) — linear weights instead of equal.
|
||
- [Indicator-Hma.md](../moving-averages/Indicator-Hma.md) — built on three WMAs for near-zero
|
||
lag.
|
||
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
|