Files
wickra/docs/wiki/Warmup-Periods.md
T
kingchenc 8aa74cb638 release(0.2.1): bump to 0.2.1, skipping Windows ARM64 this cycle
The 0.2.0 release left wickra@npm stuck at 0.1.4 and never created a
GitHub Release entry because the brand-new `wickra-win32-arm64-msvc`
sub-package name was caught by npm's spam-detection filter on its first
publish attempt (same situation that affected `wickra-win32-x64-msvc`
through 0.1.4 until npm Support unblocked it). A support ticket is open;
until it is resolved, ship 0.2.1 for the five platforms whose
sub-packages are already on npm and re-add Windows ARM64 in a follow-up
release.

Changes for this cycle:
- bindings/node/package.json: remove "wickra-win32-arm64-msvc" from
  optionalDependencies and "aarch64-pc-windows-msvc" from
  napi.triples.additional.
- bindings/node/npm/win32-arm64-msvc/: removed (will be restored fresh
  once the npm name is unblocked).
- .github/workflows/release.yml: comment out the
  aarch64-pc-windows-msvc entry of the node-build matrix with a
  TODO/restore note.
- Bump every workspace and binding version to 0.2.1 (Cargo.toml,
  pyproject.toml, bindings/node/package.json, five npm/<target>
  templates, the wiki version table). Cargo.lock regenerated.
- CHANGELOG: new [0.2.1] block consolidating every fix that has landed
  on main since 0.2.0 (HV epsilon, examples CI step, fuzz cargo-fuzz
  install, MSRV 1.85 -> 1.86 / 1.77 -> 1.88, criterion 0.5 -> 0.8,
  tokio-tungstenite 0.24 -> 0.29, tick_aggregator gap-fill cap, every
  GitHub Action SHA-pin bump). Compare-link added.

The arm64 loader branch in bindings/node/index.js is left untouched: a
Windows ARM64 user installing 0.2.1 will get the standard
`Cannot find module 'wickra-win32-arm64-msvc'` error from the loader,
which is accurate. PyPI's win-arm64 wheel is unaffected.

Verified locally:
  cargo fmt/clippy/test --workspace --all-features -> 630 passed / 0 failed
  cargo build -p wickra-examples --bins -> clean
  cargo build -p wickra-node -> clean
2026-05-23 22:20:20 +02:00

17 KiB
Raw Permalink Blame History

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

The rows are keyed by constructor, not by indicator name. Vwap appears twice — once for the cumulative Vwap::new() and once for the finite-window RollingVwap::new(period) — because the two share the indicator name Vwap (see Indicators-Overview.md) but have different warmup periods. That is the only such pair; every other row is one canonical indicator.

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 is period + 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 seed avg_gain / avg_loss. The Rust unit test that pins this is warmup_period_is_period_plus_one:
    let rsi = Rsi::new(14).unwrap();
    assert_eq!(rsi.warmup_period(), 15);
    
  • Roc::new(period) warmup is period + 1. ROC compares the current price to the price period bars ago; that comparison only makes sense starting at input period + 1.
  • Aroon::new(period) warmup is period + 1. Aroon scans a period + 1-bar window to find the bars-since-high and bars-since-low.
  • Kama::new(er_period, ...) warmup is er_period + 1. Kaufman's efficiency ratio needs er_period differences, 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

(Since wickra@0.2.1, warmupPeriod() is exposed on every Node and WASM class — single- and multi-output — alongside update(), reset() and isReady(). Consult bindings/node/index.d.ts for the authoritative TypeScript surface.)

See also