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.
| Parameter | Default | Description |
|---|---|---|
| DI length | 14 | Lookback for directional movement and true range building blocks before smoothing. |
| ADX smoothing | 14 | Wilder-style smoothing length applied to the DX series to produce ADX. |
| Trend strength threshold | 25 | Common rule-of-thumb: ADX above 25 suggests a meaningful trend; below 20 implies chop—validate per asset. |
| DI crossover sensitivity | N/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.
//@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.
| Feature | Manual Pine Script | Pineify Visual Editor |
|---|---|---|
| Correctness | Wilder smoothing is easy to implement slightly wrong. | Export aligns with ta.dmi() for consistent +DI/-DI/ADX. |
| Complexity | Multi-output indicators clutter scripts as logic grows. | Keeps DI/ADX modules organized for later strategies. |
| Regime logic | Nested if-statements for ADX gates become verbose. | Composable filters reduce cognitive load. |
| Visualization | Tables, fills, and backgrounds require repetitive setup. | Speeds up polished, publish-ready charts. |
| Iteration speed | Changing 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
Create oscillator pane
ADX is not an overlay; set overlay=false.
Call ta.dmi()
Unpack [+DI, -DI, ADX] with your chosen lengths.
Plot the trio
Use distinct colors and linewidths for readability.
Add thresholds
Draw 20/25 lines and optional background fills for regimes.
Mark DI crosses
Optionally require ADX strength or slope for valid crosses.
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.