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.
16 KiB
Warmup Periods
Every Wickra indicator returns None (Rust), None (Python), or null
(Node) for its first few inputs while it gathers enough data to produce a
defined value. The number of inputs an indicator needs before it emits its
first non-empty value is its warmup period, surfaced everywhere as
warmup_period() / warmupPeriod().
After the first emission, the indicator never goes back to a "no value yet"
state — it has rolled its state forward and will produce a steady value on
every subsequent update(). Calling reset() returns to the warming-up
state, equivalent to a freshly constructed instance.
How to read the formula column
The formulas below are taken verbatim from the warmup_period() methods in
crates/wickra-core/src/indicators/<name>.rs. The "Inputs at first
emission" column says, in 1-indexed terms, which update() call returns the
first Some/non-NaN value. They are the same number; "first emission
index" in 0-indexed terms is warmup_period − 1.
Single-output indicators
| Indicator | Constructor | Formula | warmup_period() for shown args |
Inputs at first emission |
|---|---|---|---|---|
Sma |
Sma::new(14) |
period |
14 | 14th |
Ema |
Ema::new(14) |
period |
14 | 14th |
Wma |
Wma::new(14) |
period |
14 | 14th |
Dema |
Dema::new(14) |
2 * period - 1 |
27 | 27th |
Tema |
Tema::new(14) |
3 * period - 2 |
40 | 40th |
Hma |
Hma::new(14) |
period + round(sqrt(period)).max(1) - 1 |
17 | 17th |
Kama |
Kama::new(10, 2, 30) |
er_period + 1 |
11 | 11th |
Rsi |
Rsi::new(14) |
period + 1 |
15 | 15th |
Cci |
Cci::new(20) |
period |
20 | 20th |
Roc |
Roc::new(12) |
period + 1 |
13 | 13th |
WilliamsR |
WilliamsR::new(14) |
period |
14 | 14th |
Mfi |
Mfi::new(14) |
period |
14 | 14th |
Trix |
Trix::new(15) |
3 * period - 1 |
44 | 44th |
AwesomeOscillator |
AwesomeOscillator::new(5, 34) |
slow_period |
34 | 34th |
Atr |
Atr::new(14) |
period |
14 | 14th |
Psar |
Psar::new(0.02, 0.02, 0.20) |
constant 2 |
2 | 2nd |
Obv |
Obv::new() |
constant 1 |
1 | 1st |
Vwap |
Vwap::new() |
constant 1 |
1 | 1st |
RollingVwap |
RollingVwap::new(20) |
period |
20 | 20th |
Smma |
Smma::new(14) |
period |
14 | 14th |
Trima |
Trima::new(20) |
period |
20 | 20th |
Zlema |
Zlema::new(14) |
lag + period (lag = (period − 1) / 2) |
20 | 20th |
T3 |
T3::new(5, 0.7) |
6 * period - 5 |
25 | 25th |
Vwma |
Vwma::new(20) |
period |
20 | 20th |
Mom |
Mom::new(10) |
period + 1 |
11 | 11th |
Cmo |
Cmo::new(14) |
period + 1 |
15 | 15th |
Tsi |
Tsi::new(25, 13) |
long + short |
38 | 38th |
Pmo |
Pmo::new(35, 20) |
constant 2 |
2 | 2nd |
StochRsi |
StochRsi::new(14, 14) |
rsi_period + stoch_period |
28 | 28th |
UltimateOscillator |
UltimateOscillator::new(7, 14, 28) |
max(short, mid, long) + 1 |
29 | 29th |
Ppo |
Ppo::new(12, 26) |
slow |
26 | 26th |
Dpo |
Dpo::new(20) |
max(period, period / 2 + 2) |
20 | 20th |
Coppock |
Coppock::new(14, 11, 10) |
max(roc_long, roc_short) + wma_period |
24 | 24th |
AroonOscillator |
AroonOscillator::new(14) |
period + 1 |
15 | 15th |
MassIndex |
MassIndex::new(9, 25) |
2 * ema_period + sum_period - 2 |
41 | 41st |
Natr |
Natr::new(14) |
period |
14 | 14th |
StdDev |
StdDev::new(20) |
period |
20 | 20th |
UlcerIndex |
UlcerIndex::new(14) |
2 * period - 1 |
27 | 27th |
HistoricalVolatility |
HistoricalVolatility::new(20, 252) |
period + 1 |
21 | 21st |
BollingerBandwidth |
BollingerBandwidth::new(20, 2.0) |
period |
20 | 20th |
PercentB |
PercentB::new(20, 2.0) |
period |
20 | 20th |
AtrTrailingStop |
AtrTrailingStop::new(14, 3.0) |
atr_period |
14 | 14th |
Adl |
Adl::new() |
constant 1 |
1 | 1st |
VolumePriceTrend |
VolumePriceTrend::new() |
constant 1 |
1 | 1st |
ChaikinMoneyFlow |
ChaikinMoneyFlow::new(20) |
period |
20 | 20th |
ChaikinOscillator |
ChaikinOscillator::new(3, 10) |
slow |
10 | 10th |
ForceIndex |
ForceIndex::new(13) |
period + 1 |
14 | 14th |
EaseOfMovement |
EaseOfMovement::new(14) |
period + 1 |
15 | 15th |
TypicalPrice |
TypicalPrice::new() |
constant 1 |
1 | 1st |
MedianPrice |
MedianPrice::new() |
constant 1 |
1 | 1st |
WeightedClose |
WeightedClose::new() |
constant 1 |
1 | 1st |
LinearRegression |
LinearRegression::new(14) |
period |
14 | 14th |
LinRegSlope |
LinRegSlope::new(14) |
period |
14 | 14th |
AcceleratorOscillator |
AcceleratorOscillator::classic() |
ao_slow + signal_period - 1 |
38 | 38th |
BalanceOfPower |
BalanceOfPower::new() |
constant 1 |
1 | 1st |
ChoppinessIndex |
ChoppinessIndex::new(14) |
period |
14 | 14th |
VerticalHorizontalFilter |
VerticalHorizontalFilter::new(28) |
period + 1 |
29 | 29th |
TrueRange |
TrueRange::new() |
constant 1 |
1 | 1st |
ChaikinVolatility |
ChaikinVolatility::new(10, 10) |
ema_period + roc_period |
20 | 20th |
ZScore |
ZScore::new(20) |
period |
20 | 20th |
LinRegAngle |
LinRegAngle::new(14) |
period |
14 | 14th |
Multi-output indicators
These indicators emit several values at once (a struct in Rust, a tuple in
Python, an object in Node) and every column / field transitions from "not
ready" to "ready" together — there are no rows that have a signal but no
macd, for example.
| Indicator | Constructor | Formula | warmup_period() for shown args |
Inputs at first emission | Outputs |
|---|---|---|---|---|---|
MacdIndicator |
MacdIndicator::new(12, 26, 9) |
slow + signal - 1 |
34 | 34th | macd, signal, histogram |
BollingerBands |
BollingerBands::new(20, 2.0) |
period |
20 | 20th | upper, middle, lower, stddev |
Stochastic |
Stochastic::new(14, 3) |
k_period + d_period - 1 |
16 | 16th | k, d |
Adx |
Adx::new(14) |
2 * period |
28 | 28th | plus_di, minus_di, adx |
Aroon |
Aroon::new(14) |
period + 1 |
15 | 15th | up, down |
Keltner |
Keltner::new(20, 10, 2.0) |
ema_period.max(atr_period) |
20 | 20th | upper, middle, lower |
Donchian |
Donchian::new(20) |
period |
20 | 20th | upper, middle, lower |
Vortex |
Vortex::new(14) |
period + 1 |
15 | 15th | plus, minus |
SuperTrend |
SuperTrend::new(10, 3.0) |
atr_period |
10 | 10th | value, direction |
ChandelierExit |
ChandelierExit::new(22, 3.0) |
period |
22 | 22nd | long_stop, short_stop |
ChandeKrollStop |
ChandeKrollStop::new(10, 1.0, 9) |
atr_period + stop_period - 1 |
18 | 18th | stop_long, stop_short |
"Off-by-one" cases worth memorising
A few indicators look like they should warm up at period but in fact need
period + 1 inputs. The reason is always the same — they consume diffs
or previous-close differences, not the prices themselves, and the very
first input has nothing to diff against.
Rsi::new(period)warmup isperiod + 1. RSI is based on Wilder's smoothing over per-tick gains and losses. With 14 prices you only have 13 diffs; you need 15 prices to compute 14 diffs and seedavg_gain/avg_loss. The Rust unit test that pins this iswarmup_period_is_period_plus_one:let rsi = Rsi::new(14).unwrap(); assert_eq!(rsi.warmup_period(), 15);Roc::new(period)warmup isperiod + 1. ROC compares the current price to the priceperiodbars ago; that comparison only makes sense starting at inputperiod + 1.Aroon::new(period)warmup isperiod + 1. Aroon scans aperiod + 1-bar window to find the bars-since-high and bars-since-low.Kama::new(er_period, ...)warmup iser_period + 1. Kaufman's efficiency ratio needser_perioddifferences, which costs one extra bar.
Cross-checking from your own code
The cleanest way to verify any of these from your application code is the
indicator's own warmup_period():
use wickra::{Indicator, MacdIndicator};
let macd = MacdIndicator::classic(); // (12, 26, 9)
assert_eq!(macd.warmup_period(), 34);
import wickra as ta
assert ta.MACD(12, 26, 9).warmup_period() == 34
const wickra = require('wickra');
const sma = new wickra.SMA(20);
console.log(sma.warmupPeriod()); // -> 20
(Note: as of wickra@0.1.4, warmupPeriod() is exposed on the Node
single-output classes but not on every multi-output class — consult
bindings/node/index.d.ts for the authoritative surface.)
See also
- Streaming vs Batch — the
is_ready()gate, and why alen(prices) > warmup_periodcheck is the wrong abstraction. - Indicator Chaining — how warmups stack inside a
Chain. - Source: https://github.com/kingchenc/wickra