How to Create the Average True Range (ATR) on TradingView
Measure volatility with Wilder-smoothed true range, then turn it into bands, stops, and risk metrics.
The Average True Range quantifies how far price typically moves per bar, accounting for gaps and the previous close. J. Welles Wilder Jr. introduced ATR as a robust volatility measure that powers stop placement, position sizing, and regime filters. This guide explains true range, Wilder’s smoothing method, and a full Pine Script v5 indicator built on ta.atr() with optional channels and a compact stats table.
What is Average True Range?
Average True Range (ATR) is a volatility oscillator expressed in price units. It does not indicate direction—only the degree of movement.
Each bar’s True Range (TR) is the greatest of:
- High minus low
- |High minus previous close|
- |Low minus previous close|
ATR is a smoothed average of TR, traditionally with Wilder’s RMA (same recursive form as RSI smoothing). Higher ATR implies wider swings; lower ATR suggests compression.
Why Traders Use This Indicator
- Set dynamic stops and trailing exits that scale with market noise.
- Size positions so risk per trade is consistent when volatility changes.
- Detect expansion after consolidation for breakout strategies.
- Normalize indicators or signals across symbols with different tick volatility.
| Parameter | Default | Description |
|---|---|---|
| Length | 14 | Wilder’s classic default. Shorter lengths react faster; longer lengths emphasize structural volatility. |
| Smoothing type | RMA (Wilder) | ta.atr() uses Wilder smoothing. SMA of true range is an alternative but is not identical to canonical ATR. |
| Multipliers | 1.5–3× ATR | Common multiples for stop distance or channel width; tune per market and timeframe. |
| Price context | Same symbol & session | ATR is not comparable across different contract sizes without normalization; always interpret in context. |
How ATR is calculated
True Range:
TR = max(high - low, abs(high - close[1]), abs(low - close[1]))
Wilder ATR: Let ATR[1] be the prior ATR. For period n:
ATR = ((n - 1) × ATR[1] + TR) / n
This is equivalent to an RMA of true range. In Pine Script v5, ta.atr(length) implements this definition so your script matches platform standards.
Signal Interpretation
Rising ATR: Often follows breakouts, news, or panic—risk per bar is elevated; mean-reversion systems may tighten filters.
Falling ATR: Suggests compression; breakout traders watch for expansion alongside structure breaks.
Level thresholds: Raw ATR is in price units—compare to a percentage of price or use multiples for bands rather than treating absolute numbers as universal.
Combining with Other Indicators
Pair ATR with Keltner Channels or Bollinger Bands to compare volatility envelopes. Use moving averages for trend context so ATR-based stops align with bias. Combine with volume or VWAP on intraday charts to confirm that expansions coincide with participation.
The Hard Way: Writing Pine Script Manually
Challenges of Manual Coding
Compute TR manually and feed ta.rma() to verify bit-for-bit parity with ta.atr().
Plot ATR as a percentage of close to compare volatility across differently priced assets.
Build Chandelier-style trailing stops using highest(high, n) minus ATR multiples.
Add a regime label that switches between trend and mean-reversion logic when ATR crosses its own MA.
//@version=5
indicator("ATR Tutorial (ta.atr)", shorttitle="ATR", overlay=false, format=format.price)
// -----------------------------------------------------------------------------
// Inputs
// -----------------------------------------------------------------------------
length = input.int(14, "ATR length", minval=1, tooltip="Wilder-style ATR period")
showMA = input.bool(true, "Plot ATR moving average")
maLen = input.int(20, "ATR MA length", minval=1)
showPct = input.bool(true, "Show ATR % of close (right scale)")
showBands = input.bool(false, "Show price bands on main chart (use style)")
multUpper = input.float(2.0, "Upper band multiple", step=0.25)
multLower = input.float(2.0, "Lower band multiple", step=0.25)
// -----------------------------------------------------------------------------
// Core series
// -----------------------------------------------------------------------------
atrVal = ta.atr(length)
atrMA = ta.sma(atrVal, maLen)
atrPct = close > 0 ? (atrVal / close) * 100 : na
// True range manual check (educational)
trManual = math.max(high - low, math.max(math.abs(high - close[1]), math.abs(low - close[1])))
// -----------------------------------------------------------------------------
// Pane plots
// -----------------------------------------------------------------------------
plot(atrVal, title="ATR", color=color.new(color.orange, 0), linewidth=2)
plot(showMA ? atrMA : na, title="ATR SMA", color=color.new(color.teal, 0))
hline(0, "Zero", color=color.new(color.gray, 70))
// Optional secondary scale feel: plot ATR% on same pane with distinct color
plot(showPct ? atrPct : na, title="ATR % of close", color=color.new(color.purple, 30), linewidth=1)
// -----------------------------------------------------------------------------
// Background when ATR expands vs its MA
// -----------------------------------------------------------------------------
expanding = atrVal > atrMA
bgcolor(expanding ? color.new(color.red, 92) : color.new(color.green, 92))
// -----------------------------------------------------------------------------
// Main-chart helper: copy script to overlay=false=false variant or use labels
// (Bands computed from close for demonstration)
// -----------------------------------------------------------------------------
upperDemo = close + multUpper * atrVal
lowerDemo = close - multLower * atrVal
// -----------------------------------------------------------------------------
// Alert conditions
// -----------------------------------------------------------------------------
alertcondition(ta.crossover(atrVal, atrMA), title="ATR crossed above its MA", message="ATR expansion vs short ATR average")
alertcondition(ta.crossunder(atrVal, atrMA), title="ATR crossed below its MA", message="ATR compression vs short ATR average")
// -----------------------------------------------------------------------------
// Info table
// -----------------------------------------------------------------------------
var table t = table.new(position.top_right, 2, 6, border_width=1)
if barstate.islast
table.cell(t, 0, 0, "ATR(" + str.tostring(length) + ")", text_color=color.white, bgcolor=color.new(color.gray, 35))
table.cell(t, 1, 0, str.tostring(atrVal, format.mintick), text_color=color.white)
table.cell(t, 0, 1, "ATR MA", text_color=color.white, bgcolor=color.new(color.gray, 35))
table.cell(t, 1, 1, str.tostring(atrMA, format.mintick), text_color=color.white)
table.cell(t, 0, 2, "ATR %", text_color=color.white, bgcolor=color.new(color.gray, 35))
table.cell(t, 1, 2, str.tostring(atrPct, "#.##") + "%", text_color=color.white)
table.cell(t, 0, 3, "TR (bar)", text_color=color.white, bgcolor=color.new(color.gray, 35))
table.cell(t, 1, 3, str.tostring(trManual, format.mintick), text_color=color.white)
table.cell(t, 0, 4, "Close + mult*ATR", text_color=color.white, bgcolor=color.new(color.gray, 35))
table.cell(t, 1, 4, str.tostring(upperDemo, format.mintick), text_color=color.white)
table.cell(t, 0, 5, "Close - mult*ATR", text_color=color.white, bgcolor=color.new(color.gray, 35))
table.cell(t, 1, 5, str.tostring(lowerDemo, format.mintick), text_color=color.white)
// Note: upperDemo/lowerDemo are for reference; duplicate logic in overlay=true to plot on price.Maintenance Note: The demo bands use close ± ATR multiples in a separate pane indicator. For true overlay bands, duplicate the logic in `indicator(..., overlay=true)` or link two scripts. Remove `trManual` in production if you no longer need parity checks.
The Easy Way: Build with Pineify Visual Editor
What if you could create the same indicator without writing a single line of code? Pineify is a visual editor designed for TradingView users who want professional-grade indicators through an intuitive interface.
| Feature | Manual Pine Script | Pineify Visual Editor |
|---|---|---|
| Wilder smoothing | Implement RMA of true range and handle initialization bars. | Reuse standard volatility blocks that call `ta.atr` directly. |
| Stop templates | Hand-code Chandelier/Keltner exits with subtle off-by-one risks. | Parameterize multiples and anchor lengths from a form-driven UI. |
| Cross-asset use | Normalize ATR manually for fair comparison. | Toggle ATR%, true range, and bands without rewriting math. |
| Alerts | Craft `alertcondition` for expansion and compression events. | Attach alerts to pre-defined volatility regime transitions. |
| Documentation | Comments drift from code as inputs change. | Keep labels and tooltips aligned with generated inputs. |
Faster experimentation with length, multiples, and MA filters.
Less duplicated boilerplate when cloning ATR logic into strategies.
Consistent alert messaging across teams.
Easier refactors when upgrading from studies to strategies.
Step-by-Step Tutorial
Create a new indicator
Choose a lower pane for classic ATR or overlay for band variants.
Insert ATR core
Wire `ta.atr(length)` to an input length defaulting to 14.
Add context curves
Plot a short SMA of ATR to highlight expansion vs compression.
Optional normalization
Expose ATR as a percent of price for cross-symbol review.
Define risk templates
Add multiples for stop or channel prototypes tied to ATR.
Publish alerts
Alert when ATR crosses its moving average or hits a percentile.
Export Pine v5
Save to your library and sanity-check against TradingView defaults.
Trading Strategies & Pro Tips
ATR stop and reverse
Place stops a fixed multiple of ATR away from entry and trail as new extremes form.
Pro Tip: Cap maximum stop distance during extreme spikes to avoid runaway risk.
Volatility breakout filter
Require ATR to rise above its own average before accepting breakout entries.
Position sizing by risk
Divide dollar risk per trade by ATR-derived stop distance to size contracts or shares.
Mean reversion caution
Skip fading moves when ATR percentile is elevated unless you widen targets and stops.
Common Mistakes to Avoid
- Confusing ATR with directional momentum—it only measures range, not bias.
- Using the same ATR multiple on illiquid altcoins and large-cap indices without adjustment.
- Comparing raw ATR across assets with huge price differences instead of ATR%.
- Forgetting that gaps inflate true range on the gap bar.
- Mixing SMA-of-TR with ta.atr() and expecting identical values.
Frequently Asked Questions
Generate ATR-based Pine Script faster with Pineify
- Prototype stops, channels, and regime filters without rewriting Wilder math.
- Keep alerts and inputs synchronized as you iterate.
- Export clean v5 code ready for TradingView.
Disclaimer: Trading involves significant risk. The indicators, code, and strategies provided are for educational purposes only and do not constitute financial advice. Past performance is not indicative of future results.