
Pine Script ADX Indicator: ta.adx and ta.dmi in v5
How to use ta.adx(), ta.dmi(), DI+, and DI- in Pine Script v5 — with code examples, trading filters, and strategies that actually work.
Most ADX guides online are just rehashed indicator descriptions. They explain what ADX stands for, show you the same textbook thresholds (25 = trending, below 20 = not), and call it a day. Practically useless if you're trying to actually code something in Pine Script v5.
I know this because we built a Pine Script indicator engine at Nydar, and ADX-related queries are consistently the most common thing people paste into our editor. Specifically: ta.adx(), ta.dmi(), di_plus, di_minus — the directional movement family. Traders know ADX is useful but the Pine Script documentation for these functions is thin, and the relationship between ta.adx() and ta.dmi() confuses people constantly.
So here's everything I've learned from watching hundreds of traders build ADX-based indicators on Nydar, plus the actual code to get each approach running.
What Is ta.adx() in Pine Script v5?
The ta.adx() function in Pine Script v5 calculates the Average Directional Index — a single value between 0 and 100 that measures trend strength regardless of direction. It takes three series arguments (high, low, close) and one integer for the lookback period.
Here's the simplest possible ADX indicator:
//@version=5
indicator("Basic ADX", overlay=false)
length = input.int(14, "ADX Length", minval=1)
adxValue = ta.adx(high, low, close, length)
plot(adxValue, "ADX", color=color.blue, linewidth=2)
hline(25, "Trend Threshold", color=color.gray, linestyle=hline.style_dashed)
That's it. Five lines of actual logic. The hline(25) draws a reference line at the classic threshold that J. Welles Wilder Jr. established in his 1978 book New Concepts in Technical Trading Systems — the same book that gave us RSI and ATR. Wilder's original research used daily commodity data, and the 14-period default reflects that context.
The thing most people don't realise: ta.adx() only gives you the ADX line itself. It doesn't give you DI+ or DI-. For those, you need ta.dmi().
How Does ta.dmi() Work for DI+ and DI-?
The ta.dmi() function returns three values simultaneously — positive directional indicator (DI+), negative directional indicator (DI-), and ADX — using Pine Script's tuple return syntax. It takes two parameters: the DI period length and the ADX smoothing period.
//@version=5
indicator("DMI with DI+ and DI-", overlay=false)
diLength = input.int(14, "DI Length", minval=1)
adxSmoothing = input.int(14, "ADX Smoothing", minval=1)
[diPlus, diMinus, adxLine] = ta.dmi(diLength, adxSmoothing)
plot(diPlus, "+DI", color=color.green)
plot(diMinus, "-DI", color=color.red)
plot(adxLine, "ADX", color=color.blue, linewidth=2)
hline(25, "Threshold")
The square bracket syntax [diPlus, diMinus, adxLine] is how Pine Script v5 handles multiple return values. This trips people up because it's different from most languages — you can't just call ta.dmi() and access .diPlus as a property. You have to destructure it on the left side of the assignment.
One detail that matters: ta.dmi() gives you separate control over the DI calculation period and the ADX smoothing period. With ta.adx(), those are locked together. If you set ta.adx(high, low, close, 14), both the directional movement calculation and the smoothing use 14. With ta.dmi(14, 7), you can calculate DI on 14 periods but smooth the ADX over only 7 — making it more responsive to trend changes.
That distinction is why most serious ADX strategies use ta.dmi() instead of ta.adx(). You get all three components and more parameter control.
Why Use ADX as a Trend Filter Instead of a Signal?
ADX works best as a filter — confirming whether a trend exists before you act on other signals — rather than as a standalone entry trigger. Research from Fidelity's technical analysis team found that combining ADX filtering with directional signals reduced false entries by roughly 30% compared to using crossover signals alone.
I see a lot of beginners try to trade ADX crossovers directly. DI+ crosses above DI-? Buy. DI- crosses above DI+? Sell. This generates way too many signals and most of them are garbage, especially in choppy markets. The whipsaw will eat your account.
The better approach: use ADX as a gate. Only take signals from your primary strategy when ADX confirms a real trend is present.
Here's a practical example — an EMA crossover that only fires when ADX says the market is actually trending:
//@version=5
indicator("ADX-Filtered EMA Cross", overlay=true)
fastLen = input.int(9, "Fast EMA")
slowLen = input.int(21, "Slow EMA")
adxLen = input.int(14, "ADX Length")
adxThreshold = input.int(25, "ADX Threshold")
fastEMA = ta.ema(close, fastLen)
slowEMA = ta.ema(close, slowLen)
adxVal = ta.adx(high, low, close, adxLen)
bullCross = ta.crossover(fastEMA, slowEMA) and adxVal > adxThreshold
bearCross = ta.crossunder(fastEMA, slowEMA) and adxVal > adxThreshold
plotshape(bullCross, "Buy", shape.triangleup, location.belowbar, color.green, size=size.small)
plotshape(bearCross, "Sell", shape.triangledown, location.abovebar, color.red, size=size.small)
plot(fastEMA, "Fast", color=color.green)
plot(slowEMA, "Slow", color=color.red)
Without the ADX filter, you'd get chopped to pieces during sideways consolidation. The EMA cross fires constantly when price is just ranging. Adding adxVal > adxThreshold cuts out most of those false signals. Not all of them — nothing does — but a solid majority.
We built this exact pattern into one of Nydar's AI signal models, by the way. The prediction engine uses ADX as one of its trend-strength features alongside ATR and Bollinger Band width. A 2024 analysis by Trading Strategy Guides confirmed that ADX filtering is one of the most reliable ways to avoid false breakout signals in algorithmic systems.
What ADX Values Actually Mean (Honest Version)
Most sites give you the standard table: 0-25 weak, 25-50 strong, 50-75 very strong, 75-100 extreme. That's technically accurate but it misses the point entirely.
Here's what I've actually observed watching traders use ADX on Nydar:
Below 20 — the market is going nowhere. Flat. Range-bound. Any trend-following strategy will get destroyed here. This is where mean reversion and range trading strategies work. If you're building a custom indicator on Nydar's indicator engine, check ADX first and switch logic accordingly.
20-25 — the grey zone. A trend might be forming. Or it might be a fakeout. Honestly this is the hardest range to trade. Most profitable approaches I've seen either wait for a clean break above 25 or ignore this range entirely.
25-40 — a real trend is in motion. This is where most of the money gets made with directional strategies. DI+ and DI- readings matter here — they tell you the direction that ADX alone can't.
Above 50 — strong trend, but be careful. ADX above 50 means the trend has been running hard and is statistically more likely to exhaust soon. A 2023 study published on SSRN analyzing 15 years of S&P 500 data found that ADX readings above 50 preceded trend reversals within 10 trading sessions approximately 62% of the time. Don't chase.
Above 70 — I've only seen this during events like the March 2020 crash, the GME squeeze in January 2021, or major crypto blowoffs. If your indicator hits 70+ ADX, something unusual is happening and you should be thinking about risk management, not entries.
How to Build a DI Crossover Strategy in Pine Script v5
A DI crossover strategy generates buy signals when DI+ crosses above DI- and sell signals when DI- crosses above DI+. Adding ADX as a minimum threshold filter removes the weakest signals and improves the signal-to-noise ratio.
Here's a full implementation with alert conditions:
//@version=5
indicator("DI Crossover + ADX Filter", overlay=true)
diLen = input.int(14, "DI Length")
adxSmooth = input.int(14, "ADX Smoothing")
minADX = input.int(20, "Minimum ADX")
[dp, dm, adx] = ta.dmi(diLen, adxSmooth)
longSignal = ta.crossover(dp, dm) and adx > minADX
shortSignal = ta.crossunder(dp, dm) and adx > minADX
plotshape(longSignal, "Long", shape.labelup, location.belowbar, color.green)
plotshape(shortSignal, "Short", shape.labeldown, location.abovebar, color.red)
alertcondition(longSignal, "DI+ Cross Up", "DI+ crossed above DI- with ADX > threshold")
alertcondition(shortSignal, "DI- Cross Up", "DI- crossed above DI+ with ADX > threshold")
A few things I'd tweak if this were my actual strategy:
The minADX default is 20 here, not 25. That's deliberate. If you wait for ADX to hit 25 before entering a DI crossover, you've already missed the first leg of the move. By the time ADX reads 25, price has been trending long enough for that average to climb — the best entry was bars ago. Setting the threshold at 20 catches trends earlier. You'll get more noise, but also more of the move.
Also notice I'm using alertcondition() at the bottom. These won't fire inside Nydar's engine (we handle alerts differently through the signals system), but they're standard Pine Script and will work if you're running this code elsewhere.
ADX + RSI: The Combination That Actually Works
Pairing ADX with RSI solves a problem that neither indicator handles well alone. RSI tells you when something is overbought or oversold, but it's useless in strong trends — RSI can stay above 70 for weeks during a bull run. ADX tells you if a trend exists, but not whether you should buy the dip or take profit.
Together: buy when RSI dips below 40 during a confirmed uptrend (ADX > 25, DI+ > DI-). That dip isn't weakness — it's a pullback in an active trend. Exactly the kind of entry that order flow data tends to confirm.
//@version=5
indicator("ADX + RSI Pullback", overlay=true)
rsiLen = input.int(14, "RSI Length")
diLen = input.int(14, "DI Length")
adxSmooth = input.int(14, "ADX Smoothing")
rsiVal = ta.rsi(close, rsiLen)
[dp, dm, adx] = ta.dmi(diLen, adxSmooth)
bullPullback = rsiVal < 40 and adx > 25 and dp > dm
bearPullback = rsiVal > 60 and adx > 25 and dm > dp
plotshape(bullPullback, "Bull Pullback", shape.triangleup, location.belowbar, color.green)
plotshape(bearPullback, "Bear Pullback", shape.triangledown, location.abovebar, color.red)
This is the kind of indicator I wish more people built. Instead of another oscillator that just shows you what the chart already shows, it combines two perspectives — momentum and trend strength — to surface something you wouldn't see otherwise. According to EBC Financial Group's day trading research, ADX + RSI combinations outperform single-indicator strategies by 15-25% in trending market conditions based on their 2024 backtesting analysis.
Common ta.adx() and ta.dmi() Mistakes
I've seen every possible way to break these functions. Here are the ones that come up constantly in Nydar's custom indicator editor.
Mixing up ta.adx() and ta.dmi() arguments. ta.adx() takes (high, low, close, length) — four arguments, three of which are price series. ta.dmi() takes (diLength, adxSmoothing) — two integers. People try to pass price series into ta.dmi() or forget that ta.adx() needs explicit high/low/close references. Pine Script won't always give a clear error for this.
Forgetting the tuple destructuring. If you write adx = ta.dmi(14, 14) instead of [dp, dm, adx] = ta.dmi(14, 14), you'll get a type error. The function returns three values. You have to accept all three, even if you only need one. You can use _ as a throwaway: [_, _, adx] = ta.dmi(14, 14).
Using the wrong smoothing for your timeframe. The default 14-period settings were designed by Wilder for daily charts. On a 1-minute chart, 14 bars is 14 minutes — way too short to measure meaningful trend strength. On a weekly chart, 14 bars is 14 weeks — slow to react. A rough guideline: multiply the default period by the ratio of your bar count to daily bars. For 5-minute charts, something in the 20-28 range is more useful. We went into this in our Pine Script guide in more detail.
Comparing ADX values across different assets. ADX of 30 on Bitcoin means something completely different from ADX of 30 on a utility stock. Crypto trends harder and faster. Some forex pairs barely crack 25 even during strong moves. Don't use fixed thresholds across asset classes without adjusting.
ADX on Different Timeframes: What Works Where
The way ADX behaves changes dramatically across timeframes. Imo this is the most underrated aspect of the indicator.
On daily charts, ADX is smooth, reliable, and the 14-period default works well. Wilder designed it for this. Values above 25 almost always coincide with visible trends on the chart. This is where ADX earns its reputation.
On intraday charts (1min to 15min), ADX gets noisy fast. The 14-period lookback is too short to mean anything. I've seen traders on Nydar get much better results by bumping the DI length to 20-28 and the ADX smoothing to 14-21. The cost is lag, but the reduced false signals are worth it for most day trading strategies.
On weekly/monthly charts, ADX is almost too slow. By the time it confirms a trend, you've missed 30-40% of the move. Some swing traders use it anyway as a "stay in the trade" confirmation rather than an entry trigger — if weekly ADX is above 25 and rising, don't take profits yet. That's a valid approach tbh.
Here's a quick reference:
| Timeframe | DI Length | ADX Smoothing | Threshold | Best Use | |-----------|-----------|---------------|-----------|----------| | 1-5 min | 20-28 | 14-21 | 20 | Scalping filter | | 15-60 min | 14-20 | 14 | 22-25 | Day trade filter | | Daily | 14 | 14 | 25 | Swing entry/filter | | Weekly | 10-14 | 10-14 | 20-25 | Position holding |
These aren't gospel. They're starting points from what I've seen work across the indicators people build on Nydar. Your mileage will vary depending on the asset and market conditions.
Running ADX Indicators on Nydar
One thing I should mention — you don't need a separate charting platform to run any of the code in this post. Nydar has a built-in Pine Script engine that executes ta.adx(), ta.dmi(), and the full ta.* indicator library directly on our charts. We covered the technical details of why we built it and how it works in a previous post.
The short version: paste any of these code examples into the custom indicator editor, and they'll run against live data for stocks, crypto, or forex. The ADX overlay updates in real time as new bars form. You don't need to export data, run scripts locally, or juggle between platforms.
We also ship ADX as one of the 105 built-in indicators, so if you just want the standard implementation without coding anything, it's already there. The custom engine is for when you want the filtered crossover signals, the RSI pullback combo, or any of the other custom approaches above.
Where ADX Falls Short
I'd be dishonest if I didn't say where ADX genuinely fails.
It lags. Always. It's a smoothed derivative of a smoothed derivative. By design, it can't react quickly to trend changes. If you need fast signals, look at volume delta or order flow data instead — or at least in addition to ADX.
It says nothing about direction by itself. ADX can read 40 during a crash and 40 during a rally. You need DI+ and DI- for direction. A lot of traders forget this and are confused when ADX is "high" but they don't know which way to trade.
It doesn't work in ranging markets. Not "it works less well." It genuinely doesn't work. ADX below 20 tells you to stop looking for trends, but it doesn't help you trade the range. You need different tools for that.
And honestly? For very short-term trading — scalping on 1-minute charts, say — I think ADX is mostly noise. The lag is too much relative to the holding period. I'd rather look at real-time order book depth through Level 2 data than wait for a lagging indicator to confirm what the tape already showed me 30 seconds ago.
That said, for swing trading on daily charts and as a filter for algorithmic strategies, ADX is one of the most reliable tools in the box. Wilder knew what he was doing. Just don't ask the indicator to do things it wasn't built for.
Signals generated by any indicator — including ADX — are probabilistic predictions, not financial advice. Always test strategies on paper first. Nydar offers paper trading for exactly this reason.
Last updated: March 2026
Last updated: Invalid Date