# ATR (Average True Range)

> Wilder's volatility benchmark: an exponentially-smoothed average of the
> per-bar true range that absorbs overnight gaps and is dimensioned in price
> units.

## Quick reference

| Item                | Value                                                                                |
|---------------------|--------------------------------------------------------------------------------------|
| Family | Volatility & Bands |
| Input type          | `Candle` (uses `high`, `low`, `close`)                                               |
| Output type         | `f64`                                                                                |
| Output range        | unbounded `≥ 0`                                                                      |
| Default parameters  | `period = 14` (Wilder)                                                               |
| Warmup period       | `period` (14 for defaults)                                                           |
| Interpretation      | dollar-denominated volatility scale; rises in chop and expansion                     |

## Formula

For each candle, the **true range** is
`TR_t = max(H_t - L_t, |H_t - C_{t-1}|, |L_t - C_{t-1}|)` when a previous
close is available, otherwise `TR_t = H_t - L_t` (see `Candle::true_range`
in `crates/wickra-core/src/ohlcv.rs`).

ATR is then Wilder-smoothed:

```
seed_ATR_period = (TR_1 + TR_2 + … + TR_period) / period
ATR_t           = ((period - 1) * ATR_{t-1} + TR_t) / period      for t > period
```

This is mathematically the same recursion as an EMA with `alpha = 1/period`
(Wilder smoothing), seeded with a simple mean of the first `period` true
ranges (`crates/wickra-core/src/indicators/atr.rs:58-69`).

## Parameters

| Name     | Type    | Default | Constraint | Source                              |
|----------|---------|---------|------------|-------------------------------------|
| `period` | `usize` | `14`    | `> 0`      | `Atr::new` (`atr.rs:26`)            |

Python default from `#[pyo3(signature = (period=14))]` in
`bindings/python/src/lib.rs`. `period == 0` returns `Error::PeriodZero`.

## Inputs / Outputs

```rust
impl Indicator for Atr {
    type Input  = Candle;
    type Output = f64;
    fn update(&mut self, candle: Candle) -> Option<f64>;
    fn warmup_period(&self) -> usize { self.period }
}
```

- **Rust input.** A full `Candle` struct; only `high`, `low`, and `close`
  are read (`prev_close` is cached internally between calls).
- **Python streaming.** Accepts either a 6-tuple
  `(open, high, low, close, volume, timestamp)` or a dict with keys
  `open`, `high`, `low`, `close`, `volume`, and optional `timestamp`.
- **Python batch.** `ATR.batch(high, low, close)` takes three equal-length
  `numpy.ndarray` columns and returns a 1-D `np.ndarray` with `NaN` for
  every warmup row.
- **Node streaming.** `atr.update(high, low, close)` returns `number | null`.
- **Node batch.** `atr.batch(high, low, close)` returns `Array<number>` of
  the same length, `NaN` during warmup.

## Warmup

`warmup_period() == period`. The first `period - 1` candles return `None`
(or `NaN`/`null` in batch); the `period`-th candle returns the seed value
`(TR_1 + … + TR_period) / period`. Each subsequent candle applies the
Wilder recursion.

Verified for `period = 3`: the first non-`None` output is at index `2`
(the 3rd candle).

## Edge cases

- **First candle.** `Candle::true_range(None)` falls back to `high - low`
  because there is no previous close yet. The first TR is the bar range.
- **Gaps.** With a previous close at `5.0` and a candle of `H=10, L=9`,
  `TR = max(1, 5, 4) = 5` — i.e. `|H - prev_close|` dominates. The
  pinned test `gap_up_uses_high_minus_prev_close` covers exactly this.
- **Constant input.** A series of identical candles (no gaps, fixed range)
  yields a constant ATR equal to the bar range, even before the seed is
  complete — the smoothing has nothing to smooth.
- **Non-negativity.** ATR is always `≥ 0`. The Rust test `never_negative`
  pins this property across a sinusoidal price series.
- **NaN / infinity.** `Candle::new` rejects non-finite `open`/`high`/
  `low`/`close`/`volume`; constructing the candle returns
  `Error::InvalidCandle` before it can ever reach ATR.
- **Reset.** `reset()` clears `prev_close`, the seed buffer, and the
  running average; the next call behaves as if the indicator were
  freshly constructed.

## Examples

### Rust

```rust
use wickra::{Atr, BatchExt, Candle, Indicator};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let candles = vec![
        Candle::new(10.0, 11.0, 9.0,  10.5, 1.0, 0)?,
        Candle::new(10.5, 12.0, 10.0, 11.5, 1.0, 0)?,
        Candle::new(11.5, 13.0, 11.0, 12.5, 1.0, 0)?,
        Candle::new(12.5, 14.0, 12.0, 13.5, 1.0, 0)?,
        Candle::new(13.5, 15.0, 13.0, 14.5, 1.0, 0)?,
    ];
    let mut atr = Atr::new(3)?;
    println!("{:?}", atr.batch(&candles));
    Ok(())
}
```

Output:

```
[None, None, Some(2.0), Some(2.0), Some(2.0)]
```

Every bar has range `2.0` and no gap-driven TR component, so both the
seed `(2 + 2 + 2) / 3 = 2.0` and every subsequent Wilder update stay at
`2.0`.

### Python

```python
import numpy as np
import wickra as ta

atr = ta.ATR(3)
high  = np.array([11.0, 12.0, 13.0, 14.0, 15.0])
low   = np.array([ 9.0, 10.0, 11.0, 12.0, 13.0])
close = np.array([10.5, 11.5, 12.5, 13.5, 14.5])
print(atr.batch(high, low, close))
```

Output:

```
[nan nan  2.  2.  2.]
```

### Node

```js
const w = require('wickra');

const atr = new w.ATR(3);
console.log(atr.batch(
  [11, 12, 13, 14, 15],
  [ 9, 10, 11, 12, 13],
  [10.5, 11.5, 12.5, 13.5, 14.5],
));
```

Output:

```
[ NaN, NaN, 2, 2, 2 ]
```

Streaming form (`atr.update(high, low, close)`):

```js
const w = require('wickra');

const atr = new w.ATR(3);
console.log(atr.update(11, 9,  10.5));
console.log(atr.update(12, 10, 11.5));
console.log(atr.update(13, 11, 12.5));
console.log(atr.update(14, 12, 13.5));
```

Output:

```
null
null
2
2
```

## Interpretation

- **Stop sizing.** A common pattern is "place a stop `k * ATR` away from
  entry," with `k` typically in `[1.5, 3.0]` depending on the timeframe.
  ATR's units are price, so the stop distance is directly tradable.
- **Position sizing.** `risk_per_trade / ATR` gives a quantity that
  normalises risk across assets of very different price levels.
- **Regime detection.** Persistently rising ATR signals an expansion
  regime; persistently low ATR signals consolidation, often preceding
  expansion (the volatility-of-volatility argument).

## Common pitfalls

- **Wilder smoothing vs EMA.** Wilder's smoothing factor is `1/period`,
  not the EMA's `2/(period+1)`. They look similar but produce different
  numbers; a 14-period Wilder ATR is **not** the same as a 14-period
  EMA of true range. Wickra uses the Wilder recursion explicitly.
- **Off-by-one seeding.** ATR(14) emits its first value on the 14th
  candle, not the 15th — unlike RSI(14) which needs 15 candles for 14
  diffs. The difference is that ATR's seed uses `period` true ranges
  directly (and `TR_1` is well-defined even without a previous close),
  while RSI(14) needs 14 *differences* between consecutive closes.

## References

- J. Welles Wilder Jr., *New Concepts in Technical Trading Systems*,
  Trend Research, 1978. Chapter on the Average True Range and the
  Wilder smoothing constant.

## See also

- [Bollinger Bands](../volatility-bands/Indicator-BollingerBands.md) — stddev-based volatility
  envelope around an SMA.
- [Keltner Channels](../volatility-bands/Indicator-Keltner.md) — directly composes EMA + ATR.
- [Donchian Channels](../volatility-bands/Indicator-Donchian.md) — rolling high/low without
  any smoothing.
- [PSAR](../trailing-stops/Indicator-Psar.md) — uses ATR-like volatility tracking implicitly
  through its acceleration factor.
