Average True Range: The ATR Formula Our Engine Actually Runs
The average true range takes the gap-aware bar measurement described on the true range page and smooths fourteen of them into one line. Which smoother it uses is the detail almost every explanation skips, and it changes the answer. Vike computes this as batch_atr, in the volatility family of our indicator engine.
What the average true range measures
One number, one question: how wide has a bar been lately, counting gaps?
Nothing here says anything about direction. A market falling hard and a market ripping upward produce the same reading, because every input is a distance and distances have no sign. That makes the output useful for sizing a position and useless for choosing a side, and confusing those two jobs is the most expensive mistake available with this indicator.
Note also that the scale is in price units, not percent. An ATR of 1,200 means nothing until you know whether the asset trades near 3,000 or near 90,000, which is exactly the gap batch_natr exists to close.
The exact ATR formula our engine runs
batch_atr runs, in order. The smoother is math::rma — a separate kernel from math::ema, in the same file.Here is what actually executes, rather than what the textbook diagram implies.
How Vike computes it.batch_atrbuilds the true-range series inline, then hands it tomath::rma. That helper seeds with the plain mean of the first fourteen values, writes it at index 13, and from there runs(previous × 13 + this bar) ÷ 14for every remaining bar. The parameter is calledlength, defaults to 14, and accepts anything from 1 to 1000. Before index 13 the output is not a small number or a partial average — it is nothing at all.
Thirteen missing values per series sounds trivial. Over the tape measured here it was 3,159 of 347,171 daily bars, and every one of them is a bar on which any strategy reading this line has to abstain rather than guess.
I ported the kernel out of the crate and checked it against the Python engine the crate mirrors. Running smma over true_range — the same Wilder helper the Python side exposes separately — matched across 344,012 values at 0.000e+00. So did the normalized variant against its own twin, across 343,769.
Wilder's smoothing, not an exponential one
batch_atr is involved.Two recursive smoothers live in the same file, they look identical, and they are not.
math::rma weights the newest bar at 1/n, which is 7.14% when the length is 14. math::ema weights it at 2/(n+1), which is 13.33% for the same setting — nearly twice as hard. Both seed with a simple mean over the first n values, and both write that seed at index n−1, so the shapes are easy to confuse at a glance and impossible to confuse in the output.
Just how far apart do they land? Running both over the same true-range series on 343,769 bars, the median gap was 8.75% of the ATR, the ninetieth percentile 20.31%, and the widest 88.5%. A "14-period ATR" from a platform using the wrong kernel is not a rounding error away from ours; it is a different line. Our detector uses the first one, and the exponential moving average page covers the second.
One fourteenth, in both directions
Rearrange the recursion and a consequence falls out that I use constantly.
Written out, atr[i] ÷ atr[i−1] − 1 equals (tr[i] ÷ atr[i−1] − 1) ÷ 14. Whatever the newest bar's true range is, relative to the standing average, the line moves exactly a fourteenth of the way there. Checked against every eligible bar on the tape, the largest deviation from that identity was 1.455e-11 percentage points, which is float noise and not arithmetic.
So a bar 28 times the standing ATR — the first example above is real, on DOGE — lifted the line by 196%, not by 2,700%. And a very quiet bar cannot collapse it either: the floor on a single bar's damage is −1/14, or about −7.1%, no matter how still the market goes. Shocks decay at the same rate they arrive, with a half-life of 9.35 bars.
Two ATR implementations, and which one you get
This is the part that will bite you, and it is documented in the crate's own comments.
math::atr seeds on the mean of true ranges 0 through 13 and prints its first value at index 13. math::atr_v seeds on true ranges 1 through 14 — dropping the first bar's non-gap-aware value — and prints at index 14. The second reproduces the current Python engine; the first reproduces the legacy behavior behind the unchanged Atr indicator, and nobody unified them because unifying them would silently move live output.
Who calls which is not intuitive. batch_atr and batch_keltner take the first. batch_natr, batch_supertrend and batch_chande_kroll_stop take the second. Measured on the earliest bar where both exist, the median difference across 243 symbols was 31.94% and the worst was over a hundredfold; by 100 bars in, the median is 0.027%, and by 200 it is gone. Both seeds are legitimate, they converge, and near the start of a short series they emphatically do not.
Where the ATR is blind
Every property above has a cost, and the last two examples are what it looks like.
Because a single bar moves the line by a fourteenth, the line is always describing bars that already closed. One example below sat at 30% of its own ninety-day high on the bar before the next eight printed a true range more than four times it. The reading was correct and completely uninformative about what came next.
The mirror image is just as real: after a shock, the average stays wide for roughly nine bars whether or not the market is still moving. Another example shows the line at 3.6 times the widest true range of the preceding nine bars — a stop drawn off it would have been sized for a market that had already gone quiet. The mistake I kept making early on was treating a falling ATR as permission and a rising one as a warning. It is neither. It is a description, arriving late by construction.
Frequently asked questions
What is the best ATR period?
Fourteen is the crate's default and every figure here was measured on it. Shortening the length raises the per-bar weight to 1/n, so a 7-period reading reacts twice as hard and whipsaws accordingly.
Is ATR the same as volatility?
No. It is an average distance in price units, so it rises with price even when nothing about the market's behavior changed. Use batch_natr, which divides by the close, when you need to compare two assets or two eras.
Why does my platform's ATR differ from yours? Most often the smoother: an exponential average in place of Wilder's differs by 8.75% at the median on our tape. The second suspect is the seed, which our own crate implements two ways.
Does ATR predict a breakout? Nothing on this page predicts anything. A low reading tells you the last fourteen bars were narrow, which is a statement about the past that markets are free to ignore.
How does it relate to the ADX?
DMI and ADX run the same Wilder recursion over directional movement rather than distance, and the batch_adx kernel computes its own true-range sum internally instead of calling ours.
This is educational material, not financial advice. Every number here comes from bars that have already closed, past behavior generalizes poorly, and trading carries real risk of loss — size any position so that being wrong stays survivable.