Strasmore Research
Learn Matt ConnorBy Matt Connor

What Is Average True Range (ATR)?

Average true range (ATR) measures how far a stock moves in a session, gaps included. See the true range formula and two ATR methods computed side by side.

Average true range (ATR) is the average size of a stock's move over a fixed number of sessions, measured in dollars rather than percent. Its building block, true range, takes the previous session's close into account, so an overnight gap counts as part of the move instead of vanishing. ATR tells you how far a name typically travels in a day. It says nothing about which way.

What is true range?

True range for a session is the largest of three distances:

  1. The session high minus the session low.
  2. The distance from the session high to the previous session's close.
  3. The distance from the previous session's close to the session low.

The first line is the plain daily range, the one you can read straight off a single bar in how OHLCV bars are built. The other two exist for gaps. Picture a stock that closes at $190 and opens the next morning at $178. If it then trades between $176 and $179 for the rest of the day, its high minus low is only $3, while the distance from the prior $190 close down to the $176 low is $14. True range for that session is $14. The plain range would have filed one of the year's biggest moves as a quiet day.

Taking the largest of the three also means true range can never come out smaller than the plain range: the first quantity is always in the running. On a session that opens exactly where the last one closed, the plain range wins outright and the two measures are identical.

The panel below pulls a stretch of AAPL sessions from the summer of 2025 and prints all three quantities alongside the winner. Wherever the true range line lifts off the high minus low line, a gap did the work.

QueryTrue range and its three components, AAPL daily bars, summer 2025
The exact SQL behind every number
SELECT
    toString(date)                  AS date,
    round(high_low, 2)              AS high_low,
    round(high_vs_prev_close, 2)    AS high_vs_prev_close,
    round(prev_close_vs_low, 2)     AS prev_close_vs_low,
    round(greatest(high_low, high_vs_prev_close, prev_close_vs_low), 2) AS true_range
FROM
(
    SELECT
        date,
        toFloat64(high) - toFloat64(low)             AS high_low,
        abs(toFloat64(high) - toFloat64(prev_close)) AS high_vs_prev_close,
        abs(toFloat64(prev_close) - toFloat64(low))  AS prev_close_vs_low
    FROM
    (
        SELECT
            date,
            high,
            low,
            any(close) OVER (ORDER BY date ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
        FROM
        (
            SELECT
                date,
                max(high)     AS high,
                min(low)      AS low,
                anyLast(close) AS close
            FROM global_markets.stocks_daily_aggs
            WHERE ticker = 'AAPL'
              AND date BETWEEN '2025-07-25' AND '2025-09-12'
            GROUP BY date
        )
    )
    WHERE prev_close > 0
)
ORDER BY date ASC
Run this yourself

Across the 34 sessions in that window, the last one, 2025-09-12, printed a high minus low range of $5.49 against a true range of $5.49. On gapless sessions the two sit on top of each other, which is why the separation in the chart is bursty rather than constant.

How is average true range calculated?

Wilder's original 1978 formula is a running average with a memory. Once the series has a starting value, every new bar updates it: multiply yesterday's ATR by 13, add today's true range, divide by 14. In exponential-average language that is alpha = 1/14. Today's bar gets one fourteenth of the weight and the entire prior history splits the rest. Nothing ever fully drops out.

The second convention is the one most spreadsheet tutorials teach: a simple moving average of the last 14 true ranges, equal weight on each, everything older discarded outright. Both conventions get labelled ATR(14) on a chart. They are not the same number.

The next panel computes both on one series of AAPL bars, so the divergence has nowhere to hide.

QueryATR(14) computed two ways on the same AAPL series
The exact SQL behind every number
SELECT
    toString(d)         AS date,
    round(t, 2)         AS true_range,
    round(w, 2)         AS atr_wilder_14,
    round(m, 2)         AS atr_sma_14,
    round(abs(w - m), 2) AS method_gap
FROM
(
    SELECT
        dts,
        trs,
        arrayEnumerate(trs) AS idx,
        arrayMap(i -> arrayAvg(arraySlice(trs, toInt32(i) - 13, 14)), arrayEnumerate(trs)) AS sma,
        arrayMap(i ->
                     pow(13.0 / 14.0, toInt32(i) - 14) * arrayAvg(arraySlice(trs, 1, 14))
                   + arraySum(arrayMap(k -> pow(13.0 / 14.0, toInt32(i) - toInt32(k)) * trs[k] / 14.0,
                                       range(15, i + 1))),
                 arrayEnumerate(trs)) AS wilder
    FROM
    (
        SELECT
            arrayMap(x -> x.1, s) AS dts,
            arrayMap(x -> x.2, s) AS trs
        FROM
        (
            SELECT arraySort(x -> x.1, groupArray((date, tr))) AS s
            FROM
            (
                SELECT
                    date,
                    greatest(toFloat64(high) - toFloat64(low),
                             abs(toFloat64(high) - toFloat64(prev_close)),
                             abs(toFloat64(prev_close) - toFloat64(low))) AS tr
                FROM
                (
                    SELECT
                        date,
                        high,
                        low,
                        any(close) OVER (ORDER BY date ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
                    FROM
                    (
                        SELECT
                            date,
                            max(high)      AS high,
                            min(low)       AS low,
                            anyLast(close) AS close
                        FROM global_markets.stocks_daily_aggs
                        WHERE ticker = 'AAPL'
                          AND date BETWEEN '2025-06-02' AND '2025-09-12'
                        GROUP BY date
                    )
                )
                WHERE prev_close > 0
            )
        )
    )
)
ARRAY JOIN dts AS d, trs AS t, sma AS m, wilder AS w, idx AS i
WHERE i >= 14
ORDER BY d ASC
Run this yourself

Read the two smoothed lines together and the mechanical difference shows up plainly. The simple average steps up the day a large true range enters the 14-bar window, holds that level flat, then steps down 14 sessions later when the same bar falls out the back. Wilder's version absorbs the bar once and decays it forever after. On 2025-09-12 the Wilder calculation printed $4.74 and the simple average printed $4.56, leaving the two $0.18 apart on identical inputs.

Why does ATR differ between platforms?

Wilder's recursion needs a first value before it can update anything, and the formula does not supply one. Two seeds are common: the simple average of the first 14 true ranges (Wilder's own choice), or the first true range on its own. Each sends the curve down a slightly different path, and the exponential weighting means the starting value never fully leaves. It fades by a factor of 13/14 per bar, a little over 7 percent of what remains per session, which takes dozens of bars to become invisible.

The panel below runs the identical recursion twice on one series and changes nothing but the seed.

QuerySame Wilder recursion, two seed values, AAPL daily bars
The exact SQL behind every number
SELECT
    toString(d)                       AS date,
    round(a, 2)                       AS atr_seed_sma_14,
    round(b, 2)                       AS atr_seed_first_bar,
    round(100 * abs(a - b) / a, 2)    AS seed_gap_pct
FROM
(
    SELECT
        dts,
        arrayEnumerate(trs) AS idx,
        arrayMap(i ->
                     pow(13.0 / 14.0, toInt32(i) - 14) * arrayAvg(arraySlice(trs, 1, 14))
                   + arraySum(arrayMap(k -> pow(13.0 / 14.0, toInt32(i) - toInt32(k)) * trs[k] / 14.0,
                                       range(15, i + 1))),
                 arrayEnumerate(trs)) AS seed_sma,
        arrayMap(i ->
                     pow(13.0 / 14.0, toInt32(i) - 1) * trs[1]
                   + arraySum(arrayMap(k -> pow(13.0 / 14.0, toInt32(i) - toInt32(k)) * trs[k] / 14.0,
                                       range(2, i + 1))),
                 arrayEnumerate(trs)) AS seed_first
    FROM
    (
        SELECT
            arrayMap(x -> x.1, s) AS dts,
            arrayMap(x -> x.2, s) AS trs
        FROM
        (
            SELECT arraySort(x -> x.1, groupArray((date, tr))) AS s
            FROM
            (
                SELECT
                    date,
                    greatest(toFloat64(high) - toFloat64(low),
                             abs(toFloat64(high) - toFloat64(prev_close)),
                             abs(toFloat64(prev_close) - toFloat64(low))) AS tr
                FROM
                (
                    SELECT
                        date,
                        high,
                        low,
                        any(close) OVER (ORDER BY date ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
                    FROM
                    (
                        SELECT
                            date,
                            max(high)      AS high,
                            min(low)       AS low,
                            anyLast(close) AS close
                        FROM global_markets.stocks_daily_aggs
                        WHERE ticker = 'AAPL'
                          AND date BETWEEN '2025-06-02' AND '2025-08-01'
                        GROUP BY date
                    )
                )
                WHERE prev_close > 0
            )
        )
    )
)
ARRAY JOIN dts AS d, seed_sma AS a, seed_first AS b, idx AS i
WHERE i >= 14
ORDER BY d ASC
Run this yourself

At the first printed session, 2025-06-23, the two curves stand 11.99 percent apart. By 2025-08-01, 29 sessions of smoothing later, they are still 1.5 percent apart on the same stock and the same bars. Now add the real-world wrinkle: platforms do not agree on how much history to load. One that starts its recursion 200 bars back and one that starts 5,000 bars back are running different seeds by definition, and both are following Wilder correctly. This is the same class of disagreement described in why RSI differs between platforms. Wilder built both indicators on the same recursive average, and the seeding problem travels with it.

Is ATR a volatility measure?

ATR is a volatility measure denominated in dollars. That unit is the point and also the trap. A $4 average true range is enormous on a $30 stock and unremarkable on a $600 one, so ATR compares poorly across names until you divide it by price. The percentage version is what makes a cross-name reading mean anything.

Query14-session average true range as a share of price, six household names
The exact SQL behind every number
SELECT
    ticker,
    round(avg(tr), 2)                              AS atr_14_dollars,
    round(100 * avg(tr) / avg(toFloat64(close)), 2) AS atr_pct
FROM
(
    SELECT
        ticker,
        close,
        greatest(toFloat64(high) - toFloat64(low),
                 abs(toFloat64(high) - toFloat64(prev_close)),
                 abs(toFloat64(prev_close) - toFloat64(low))) AS tr
    FROM
    (
        SELECT
            ticker,
            date,
            high,
            low,
            close,
            any(close) OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
        FROM
        (
            SELECT
                ticker,
                date,
                max(high)      AS high,
                min(low)       AS low,
                anyLast(close) AS close
            FROM global_markets.stocks_daily_aggs
            WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'SPY', 'KO', 'TSLA')
              AND date BETWEEN '2025-08-22' AND '2025-09-12'
            GROUP BY ticker, date
        )
    )
    WHERE prev_close > 0
)
GROUP BY ticker
ORDER BY atr_pct DESC
Run this yourself

Over the 14 sessions ending in mid-September 2025, TSLA sits at the top of this list at 3.83 percent of price, and SPY at the bottom at 0.74 percent. The dollar column beside it is worth a glance for contrast: in raw dollars the same six names order themselves differently, since a high share price inflates the figure on its own. ATR is built entirely from bars that already printed, which places it on the realized side of the divide covered in historical volatility vs implied volatility. Implied volatility comes out of option prices and looks forward instead.

What is ATR actually used for?

The use that survives every calculation dispute above is ATR as a unit of measurement. A fixed 5 percent stop means something different on every symbol it touches. A stop placed 2 ATR from entry means the same thing everywhere: twice the distance the stock typically covers in a session, so it sits a wider dollar distance away on a fast name and a narrower one on a sleepy name.

Position size follows the same arithmetic. A trader working with a fixed dollar risk per trade divides that amount by the ATR-based stop distance to get a share count, which mechanically hands fewer shares to the widest-ranging names. That is the mechanism inside volatility targeting position sizing, with ATR standing in as the volatility estimate.

Two cautions ride along. ATR is a lagging average of range that has already occurred, so it widens after a turbulent stretch rather than ahead of one. And it carries no direction at all: a rising ATR is equally consistent with a rally and a slide. For a read on same-day participation rather than range, ATR is usually paired with something like relative volume.

FAQ

What is a good ATR value?

There is no universal level. ATR is quoted in dollars, so its size scales with the price of the stock and with the volatility of the name. Divide it by price for a figure that can be compared across symbols, as the six-name panel above does.

Is ATR the same as volatility?

Not quite. ATR measures realized range in dollars per session, gaps included. Standard-deviation volatility measures the dispersion of percentage returns and is normally annualized. Both look backward at data that has already printed, and they answer in different units.

What is the default ATR period?

Fourteen sessions, carried over from Wilder's 1978 book. A shorter lookback such as 7 reacts faster when the range changes; a longer one such as 20 or 50 stays steadier and takes longer to register a new regime.

Why does my ATR not match another chart?

Two usual culprits. The smoothing method may differ (Wilder's exponential update versus a simple average of true range), and the seed value that started the recursion depends on how much history the platform loaded. The panels above show both effects on one series.

Can ATR be calculated on intraday bars?

Yes. True range is defined against the previous bar's close, whatever length that bar is. On intraday bars the overnight gap lands entirely in the first bar of the session, which makes that bar's true range far larger than the ones that follow it.

Data notes

Every panel pins a fixed 2025 date range, so these numbers do not move as new sessions print. Daily bars are deduplicated per session before the calculation, and the previous close is read from the prior row of the same symbol. Both ATR columns use a 14-session lookback and are rounded in SQL. The Wilder column is computed in closed form, which is arithmetically identical to updating it bar by bar.


Every panel here ships with the SQL that produced it, so you can see exactly which of the three distances won on any session. To run the same true range calculation on a symbol and window of your choosing, ask for it in plain English on the Strasmore terminal.