Indicator TutorialPine Script 5

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.
ParameterDefaultDescription
Length14Wilder’s classic default. Shorter lengths react faster; longer lengths emphasize structural volatility.
Smoothing typeRMA (Wilder)ta.atr() uses Wilder smoothing. SMA of true range is an alternative but is not identical to canonical ATR.
Multipliers1.5–3× ATRCommon multiples for stop distance or channel width; tune per market and timeframe.
Price contextSame symbol & sessionATR 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.

Pine Script 5
//@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.

FeatureManual Pine ScriptPineify Visual Editor
Wilder smoothingImplement RMA of true range and handle initialization bars.Reuse standard volatility blocks that call `ta.atr` directly.
Stop templatesHand-code Chandelier/Keltner exits with subtle off-by-one risks.Parameterize multiples and anchor lengths from a form-driven UI.
Cross-asset useNormalize ATR manually for fair comparison.Toggle ATR%, true range, and bands without rewriting math.
AlertsCraft `alertcondition` for expansion and compression events.Attach alerts to pre-defined volatility regime transitions.
DocumentationComments 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

1

Create a new indicator

Choose a lower pane for classic ATR or overlay for band variants.

2

Insert ATR core

Wire `ta.atr(length)` to an input length defaulting to 14.

3

Add context curves

Plot a short SMA of ATR to highlight expansion vs compression.

4

Optional normalization

Expose ATR as a percent of price for cross-symbol review.

5

Define risk templates

Add multiples for stop or channel prototypes tied to ATR.

6

Publish alerts

Alert when ATR crosses its moving average or hits a percentile.

7

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.