Versions bumped to 0.1.5 in every authoritative location:
- workspace `Cargo.toml` (`[workspace.package].version`, the
`wickra-core` path dependency pin).
- `bindings/python/pyproject.toml`.
- `bindings/node/package.json` (main + all six `optionalDependencies`
pins).
- All six per-platform `bindings/node/npm/<target>/package.json`
templates.
CHANGELOG: the accumulated `[Unreleased]` block is promoted to
`[0.1.5] - TBD` (date left for the user to set at tag time); the new
`[Unreleased]` header sits empty above it; the compare link table is
extended with `[0.1.5]: …compare/v0.1.4...v0.1.5` and the
`[Unreleased]` link is repointed to `…compare/v0.1.5...HEAD`.
Wiki refresh for 0.1.5 (R20 + Z2):
- `Home.md` version pin table updated; the Quickstart-Node hint replaces
the "spam filter holding back Windows" caveat with "0.1.5 is the
first release in which `npm install wickra` works end-to-end on
Windows" (npm Support released the name on 2026-05-22).
- `Quickstart-Node.md`'s Windows caveat is rewritten to explain the
history (`0.1.1`–`0.1.4` of `wickra-win32-x64-msvc` are burned) and
the resolution (0.1.5+ installs cleanly).
- `Quickstart-Rust.md` version mention bumped.
- `Warmup-Periods.md` note bumped + corrected: every Node and WASM
class — single- and multi-output — now exposes `warmupPeriod()` after
R3 (this branch), not only the single-output ones.
`release.yml` `publish_dir` no longer silently swallows a
second-attempt platform-package publish failure with a `::warning::`
and `return 0`. A real failure (after the existing 30s retry) now
emits an `::error::` and fails the job. The original mask is exactly
what allowed the `wickra-win32-x64-msvc@0.1.1–0.1.4` spam-filter
rejections to land four times in a row without anyone noticing (audit
finding R20). Failing loud means the next regression of this shape is
caught at the release run, not by a Windows user trying to
`require('wickra')`.
This commit does NOT push, tag, or trigger a release — the user
publishes the 0.1.5 tag themselves once the manual npm-republish
smoke test confirms `wickra-win32-x64-msvc@0.1.5` accepts publish on
the freshly-released name.
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
(Since wickra@0.1.5, 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
- 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