Indicator TutorialPine Script v5

How to Code ADX and Directional Movement in Pine Script

DI+/DI-, ADX smoothing, and practical trend-strength thresholds

The Average Directional Index (ADX) quantifies trend strength without indicating direction. Paired with Plus Directional Indicator (+DI) and Minus Directional Indicator (-DI), it forms the directional movement system popularized by J. Welles Wilder. This guide explains true range, directional movement, smoothed averages, and a production-ready Pine Script v5 example using ta.dmi() with optional threshold highlights and alerts.

What Is ADX?

ADX averages the absolute expansion of directional movement over time, producing a 0–100 scaled measure of trend strength. Rising ADX suggests strengthening trend impulse; falling ADX points to weakening trends or chop.

+DI and -DI compare upward versus downward movement relative to true range, giving a directional cue when they cross.

Key distinction

ADX answers “how strong?” while DI crossovers answer “which way is pressure leaning?”—professional systems often require both to align.

Why Traders Use This Indicator

  • Filter strategies so entries fire only when trends are statistically strong.
  • Avoid mean-reversion fades when ADX climbs above 25–30.
  • Pair DI crosses with ADX rising for breakout-style confirmation.
  • Standardize regime detection across equities, FX, and crypto.
ParameterDefaultDescription
DI length14Lookback for directional movement and true range building blocks before smoothing.
ADX smoothing14Wilder-style smoothing length applied to the DX series to produce ADX.
Trend strength threshold25Common rule-of-thumb: ADX above 25 suggests a meaningful trend; below 20 implies chop—validate per asset.
DI crossover sensitivityN/A (market driven)DI lines whip in ranges; combine crosses with ADX slope or price structure.

How ADX and DI Are Calculated

True Range (TR) extends the classic range with gap awareness: max(high − low, |high − prevClose|, |low − prevClose|).

Plus DM / Minus DM capture directional expansion when today’s high/low exceeds the prior bar’s extremes by at least a threshold; otherwise DM is zero.

Smoothed TR, +DM, −DM use Wilder smoothing (roughly analogous to EMA with α = 1/length).

+DI and −DI: 100 × smoothed(+DM) / smoothed(TR) and similarly for minus.

DX and ADX: DX normalizes DI separation; ADX smooths DX. Pine’s ta.dmi(diLength, adxSmoothing) returns [+DI, −DI, ADX] consistent with these rules.

Signal Interpretation

Rising ADX: Trend strength increasing—breakout and trend systems often favor this environment.

Falling ADX: Trend decay—range strategies may become more relevant.

ADX below 20–25: Weak or absent trend; DI crosses may be noisy.

+DI > −DI: Bullish directional pressure; crosses can mark shifts when ADX confirms.

−DI > +DI: Bearish directional pressure; treat similarly with confirmation.

Combining with Other Indicators

Use ADX as a regime gate for moving-average or breakout systems.

Pair with RSI or MACD for momentum, but only take momentum signals when ADX supports trending conditions.

Volume or OBV can validate that DI-driven moves coincide with participation.

The Hard Way: Writing Pine Script Manually

Challenges of Manual Coding

Implement Wilder smoothing manually and compare +DI/-DI/ADX to ta.dmi().

Plot ADX slope (ADX - ADX[1]) as a histogram for second-derivative insight.

Require ADX > 25 and ADX rising for two bars before accepting DI crosses.

Build a heatmap background by ADX deciles for quick regime scanning.

Log DI cross frequency conditioned on ADX buckets in a strategy test.

Pine Script v5
//@version=5
indicator("ADX Tutorial (ta.dmi)", shorttitle="ADX", overlay=false)

// -----------------------------------------------------------------------------
// Inputs
// -----------------------------------------------------------------------------
diLen = input.int(14, "DI length", minval=1)
adxLen = input.int(14, "ADX smoothing", minval=1)
strong = input.float(25.0, "Trend strength threshold", step=1.0)
weak = input.float(20.0, "Chop threshold", step=1.0)
showBg = input.bool(true, "Highlight regime background")
showMarkers = input.bool(true, "Mark DI crosses when ADX strong")

// -----------------------------------------------------------------------------
// Core: [+DI, -DI, ADX]
// -----------------------------------------------------------------------------
[plusDI, minusDI, adx] = ta.dmi(diLen, adxLen)

// -----------------------------------------------------------------------------
// Derived conditions
// -----------------------------------------------------------------------------
bullCross = ta.crossover(plusDI, minusDI)
bearCross = ta.crossunder(plusDI, minusDI)
adxRising = adx > adx[1]
strongTrend = adx >= strong
chop = adx <= weak

// -----------------------------------------------------------------------------
// Plots
// -----------------------------------------------------------------------------
plot(plusDI, "+DI", color=color.new(color.teal, 0), linewidth=2)
plot(minusDI, "-DI", color=color.new(color.orange, 0), linewidth=2)
plot(adx, "ADX", color=color.new(color.silver, 0), linewidth=2)

hline(strong, "Strong (25)", color=color.new(color.green, 50))
hline(weak, "Weak (20)", color=color.new(color.red, 60))

plotshape(showMarkers and bullCross and strongTrend, "Bull DI cross", shape.triangleup, location.bottom, color.new(color.teal, 0), size=size.tiny, text="+DI")
plotshape(showMarkers and bearCross and strongTrend, "Bear DI cross", shape.triangledown, location.top, color.new(color.orange, 0), size=size.tiny, text="-DI")

bgcolor(showBg and strongTrend ? color.new(color.green, 92) : showBg and chop ? color.new(color.gray, 93) : na)

// -----------------------------------------------------------------------------
// Data window: spread and slope
// -----------------------------------------------------------------------------
spread = math.abs(plusDI - minusDI)
adxSlope = adx - adx[1]
plot(spread, "DI spread (data window)", display=display.data_window)
plot(adxSlope, "ADX slope (data window)", display=display.data_window)

// -----------------------------------------------------------------------------
// Alerts
// -----------------------------------------------------------------------------
alertcondition(bullCross, "DI bullish cross", "+DI crossed above -DI")
alertcondition(bearCross, "DI bearish cross", "+DI crossed below -DI")
alertcondition(ta.cross(adx, strong), "ADX cross 25", "ADX crossed trend-strength threshold")

// -----------------------------------------------------------------------------
// Summary table
// -----------------------------------------------------------------------------
var table tab = table.new(position.top_right, 1, 4, bgcolor=color.new(color.black, 78))
if barstate.islast
    table.cell(tab, 0, 0, "ADX: " + str.tostring(adx, "#.##"), text_color=color.white)
    table.cell(tab, 0, 1, "+DI: " + str.tostring(plusDI, "#.##"), text_color=color.teal)
    table.cell(tab, 0, 2, "-DI: " + str.tostring(minusDI, "#.##"), text_color=color.orange)
    table.cell(tab, 0, 3, strongTrend ? "Regime: Trending" : chop ? "Regime: Chop" : "Regime: Mid", text_color=color.silver)

Maintenance Note: Thresholds like 25 are heuristics, not laws—recalibrate per market. If TradingView adjusts ta.dmi() internals, revalidate against published Wilder DM formulas on a sample window.

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
CorrectnessWilder smoothing is easy to implement slightly wrong.Export aligns with ta.dmi() for consistent +DI/-DI/ADX.
ComplexityMulti-output indicators clutter scripts as logic grows.Keeps DI/ADX modules organized for later strategies.
Regime logicNested if-statements for ADX gates become verbose.Composable filters reduce cognitive load.
VisualizationTables, fills, and backgrounds require repetitive setup.Speeds up polished, publish-ready charts.
Iteration speedChanging lengths touches many dependent lines.Central inputs propagate safely through the graph.

Build ADX-gated systems without retyping DI math.

Maintain readable projects as you add confirmations.

Export Pine v5 suitable for publishing and team review.

Pair ADX with other Pineify modules for risk and execution.

Step-by-Step Tutorial

1

Create oscillator pane

ADX is not an overlay; set overlay=false.

2

Call ta.dmi()

Unpack [+DI, -DI, ADX] with your chosen lengths.

3

Plot the trio

Use distinct colors and linewidths for readability.

4

Add thresholds

Draw 20/25 lines and optional background fills for regimes.

5

Mark DI crosses

Optionally require ADX strength or slope for valid crosses.

6

Export alerts

Wire alertconditions for crosses and ADX threshold breaks.

Trading Strategies & Pro Tips

ADX-gated breakouts

Enter breakouts only if ADX > 25 and rising, with +DI > -DI for longs (mirror for shorts).

Pro Tip: Add a minimum hold to avoid immediate stop-outs on retests.

Trend filter for MA systems

Allow MA cross entries solely when ADX exceeds your threshold, skipping chop.

DI crossover with structure

Take DI bullish crosses only if price holds above a recent swing high.

Mean reversion when ADX fades

As ADX drops below 20 after a trend, consider range fades with tight risk—confirm with price action.

Common Mistakes to Avoid

  • Treating ADX direction as bullish or bearish—ADX is non-directional.
  • Trading raw DI crosses in low ADX environments.
  • Using identical thresholds on every asset without validation.
  • Ignoring that ADX is lagging; spikes often follow the move.
  • Mixing multiple ADX lengths on one chart without labeling clearly.

Frequently Asked Questions

Build ADX-filtered indicators faster with Pineify

  • Compose ta.dmi() outputs with gates, alerts, and tables quickly.
  • Export maintainable Pine v5 for teams.
  • Layer trend filters across modules without spaghetti code.

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.