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 > periodThis 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
use wickra::{Indicator, Atr, Candle};
// Atr: Input = Candle, Output = f64
const _: fn(&mut Atr, Candle) -> Option<f64> = <Atr as Indicator>::update;- Rust input. A full
Candlestruct; onlyhigh,low, andcloseare read (prev_closeis cached internally between calls). - Python streaming. Accepts either a 6-tuple
(open, high, low, close, volume, timestamp)or a dict with keysopen,high,low,close,volume, and optionaltimestamp. - Python batch.
ATR.batch(high, low, close)takes three equal-lengthnumpy.ndarraycolumns and returns a 1-Dnp.ndarraywithNaNfor every warmup row. - Node streaming.
atr.update(high, low, close)returnsnumber | null. - Node batch.
atr.batch(high, low, close)returnsArray<number>of the same length,NaNduring 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 tohigh - lowbecause there is no previous close yet. The first TR is the bar range. - Gaps. With a previous close at
5.0and a candle ofH=10, L=9,TR = max(1, 5, 4) = 5— i.e.|H - prev_close|dominates. The pinned testgap_up_uses_high_minus_prev_closecovers 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 testnever_negativepins this property across a sinusoidal price series. - NaN / infinity.
Candle::newrejects non-finiteopen/high/low/close/volume; constructing the candle returnsError::InvalidCandlebefore it can ever reach ATR. - Reset.
reset()clearsprev_close, the seed buffer, and the running average; the next call behaves as if the indicator were freshly constructed.
Examples
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
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
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)):
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
2Interpretation
- Stop sizing. A common pattern is "place a stop
k * ATRaway from entry," withktypically 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 / ATRgives 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's2/(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
periodtrue ranges directly (andTR_1is 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 — stddev-based volatility envelope around an SMA.
- Keltner Channels — directly composes EMA + ATR.
- Donchian Channels — rolling high/low without any smoothing.
- PSAR — uses ATR-like volatility tracking implicitly through its acceleration factor.