# 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 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`:
  ```rust
  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()`:

```rust
use wickra::{Indicator, MacdIndicator};
let macd = MacdIndicator::classic();   // (12, 26, 9)
assert_eq!(macd.warmup_period(), 34);
```

```python
import wickra as ta
assert ta.MACD(12, 26, 9).warmup_period() == 34
```

```javascript
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](Streaming-vs-Batch.md) — the `is_ready()` gate, and
  why a `len(prices) > warmup_period` check is the wrong abstraction.
- [Indicator Chaining](Indicator-Chaining.md) — how warmups stack inside a
  `Chain`.
- Source: <https://github.com/kingchenc/wickra>
