Skip to content

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

ItemValue
FamilyVolatility & Bands
Input typeCandle (uses high, low, close)
Output typef64
Output rangeunbounded ≥ 0
Default parametersperiod = 14 (Wilder)
Warmup periodperiod (14 for defaults)
Interpretationdollar-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

NameTypeDefaultConstraintSource
periodusize14> 0Atr::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
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 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 — 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.