Trend strength · Two smoothing lengths · Pine Script v6
Average Directional Index: Pine Script Calculation and Limits
ADX summarizes the imbalance between smoothed upward and downward directional movement. It can rise during a price decline as well as a rally. Inspect the calculation and its timing before using a threshold as a trading filter.
The retained custom example plots ADX with fixed lengths of 14. The second example uses TradingView's built-in ta.dmi function and exposes both lengths. Neither example is a backtest or a set of trade instructions.
From directional movement to ADX
For a bar with known previous high and low, define upward change as current high minus previous high, and downward change as previous low minus current low. Retain upward change only when it is positive and greater than downward change. Retain downward change only when it is positive and greater than upward change. Otherwise set that directional movement to zero. Equal positive changes contribute zero to both sides.
This comparison matters on an outside bar. With previous high 100 and low 95, then current high 103 and low 93, upward change is 3 and downward change is 2. The result is +DM = 3 and -DM = 0, not 3 and 2. An inside bar contributes neither direction.
TR = max(H − L, |H − previous C|, |L − previous C|)
+DI = 100 × RMA(+DM, n) / RMA(TR, n)
−DI = 100 × RMA(−DM, n) / RMA(TR, n)
DX = 100 × |+DI − −DI| / (+DI + −DI)
ADX = RMA(DX, m)
Wilder-style RMA uses weight 1 / length on the new observation after initialization. The DI length n smooths directional movement and true range; the ADX length m smooths DX. Fourteen is a bar count, not two calendar weeks, and recursive smoothing retains information from older bars beyond the initial window.
The displayed custom code substitutes denominator 1 when the DI sum is zero, giving DX zero in that case. It uses fixnan on the DI series, which can carry a previous defined value through a missing calculation. Check initialization and flat or missing-data stretches instead of assuming every plotted point represents fresh valid input.
A numerical check: strength has no sign
These are synthetic intermediate values, not observed market results. If +DI is 30 and -DI is 10, DX is 100 × 20 / 40 = 50. Swapping them to +DI 10 and -DI 30 still gives DX 50. ADX cannot recover the discarded direction.
Assume an already initialized ADX of 20 and smoothing length 14. With the next DX equal to 50, ADX becomes (13 × 20 + 50) / 14 = 22.142857. With DX equal to 10 instead, it becomes 19.285714. ADX rises when the new DX exceeds its prior smoothed value, not simply whenever the two DI lines move farther apart in absolute points.
| Observation | Supported interpretation | Not established |
|---|---|---|
| ADX exceeds 25 | A conventional strength reference has been exceeded | Trade profitability or bullish direction |
| ADX is below 20 | Smoothed relative directional imbalance is low | That all trend strategies must lose |
| ADX falls while price rises | The smoothed imbalance is decreasing | A scheduled price reversal |
| ADX exceeds 50 | A high reading relative to its 0–100 scale | Exhaustion within a fixed number of bars |
TradingView documents 20 and 25 as conventional interpretation levels. Their usefulness in a strategy depends on the complete rule, market, period and costs. There is no verified win rate or signal-frequency benchmark for the examples here.
Retained custom ADX example
This script calls p_ta_adx(14, 14) with constants and draws one red line. It does not expose those lengths in the Inputs panel, draw threshold lines, create alerts or submit orders. The time-range and custom-source helpers are retained in the source but are not used by the ADX calculation.
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//@version=6
indicator(title="Average Directional Index", overlay=false, max_labels_count=500)
p_comm_time_range_to_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
int start_unix_time = na
int end_unix_time = na
int start_time_hour = na
int start_time_minute = na
int end_time_hour = na
int end_time_minute = na
if str.length(time_range) == 11
start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
else if str.length(time_range) == 9
start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
start_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)
end_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)
[start_unix_time, end_unix_time]
p_custom_sources() =>
[open, high, low, close, volume]
p_ta_dirmov(len) =>
up = ta.change(high)
down = -ta.change(low)
plusDM = na(up) ? na : (up > down and up > 0 ? up : 0)
minusDM = na(down) ? na : (down > up and down > 0 ? down : 0)
truerange = ta.rma(ta.tr, len)
plus = fixnan(100 * ta.rma(plusDM, len) / truerange)
minus = fixnan(100 * ta.rma(minusDM, len) / truerange)
[plus, minus]
p_ta_adx(simple int dilen, simple int adxlen) =>
[plus, minus] = p_ta_dirmov(dilen)
sum = plus + minus
adx = 100 * ta.rma(math.abs(plus - minus) / (sum == 0 ? 1 : sum), adxlen)
p_ind_1 = p_ta_adx(14, 14)
plot(p_ind_1, "ADX", color.rgb(255, 82, 82, 0), 1)
The built-in ta.dmi tuple
TradingView provides ta.dmi(diLength, adxSmoothing), returning +DI, -DI and ADX in that order. Use the documented function rather than assuming there is a standalone ta.adx function. The custom p_ta_adx name above belongs to the example itself.
//@version=6
indicator("TradingView Built-in ta.dmi() ADX Demo [Pineify]", overlay=false)
int diLength = input.int(14, "DI Length")
int adxSmoothing = input.int(14, "ADX Smoothing")
// 1. ta.dmi() returns a tuple of 3 series: [+DI, -DI, ADX]
[diPlus, diMinus, adxValue] = ta.dmi(diLength, adxSmoothing)
// 2. Extract ONLY the ADX value by discarding +DI and -DI with underscores (_)
[_, _, adxOnly] = ta.dmi(diLength, adxSmoothing)
// 3. Plotting the results
plot(adxValue, "ADX", color=color.red, linewidth=2)
plot(diPlus, "+DI", color=color.green, linewidth=1)
plot(diMinus, "-DI", color=color.orange, linewidth=1)
hline(25, "Strong Trend Threshold", color=color.gray, linestyle=hline.style_dashed)The second example exposes both lengths, plots all three returned series and draws a horizontal line at 25. Its second tuple call demonstrates discarding values with underscores; adxOnly is not plotted. Keep both input values positive: the retained declarations do not impose minval. The label on the 25 line is a reference convention, not evidence that any trade qualifies.
Compare settings without inventing an optimum
Hold the instrument, session, interval, data history and evaluation rule constant. Change one smoothing length at a time and record its actual effect on values and confirmation times. A longer RMA length places less weight on the latest value; it does not imply a fixed reduction in false signals or a fixed number of delayed bars.
Use standard OHLC candles for calculation checks. A five-minute bar and a daily bar aggregate different observations; multiplying an intraday period count does not generally reproduce the daily indicator. There is no rule here that requires crypto charts to use four hours or that makes lower intervals unusable.
- Open Pine Editor from TradingView's right-side Pine icon, add an example and compare completed bars with the built-in DMI indicator using matching lengths.
- Record +DI, -DI, DX and ADX where available. Check outside bars, equal high/low expansions, zero directional movement and initial unavailable values.
- Compare the two examples after warmup; investigate any difference in data initialization or missing-value handling before attributing it to signal quality.
- Observe the current open bar separately. Its high, low and close can change, so use completed bars when testing a close-confirmed condition.
Turn filter ideas into testable rules
An ADX filter does not supply direction, a fill price or an exit. These are research questions to specify before testing, not recommended strategies or claimed results.
Trend continuation
For an experiment combining price above a 20-bar EMA with ADX above 25, distinguish a persistent state from the first qualifying bar. Specify entry timing, repeated-entry handling, exits, position size and costs before comparing the same strategy without the ADX filter.
Possible exhaustion
A decline from ADX above 50 does not establish a reversal. Define the independent price condition, confirmation time and horizon first; record cases where the old price trend continues. Avoid identifying a peak using future bars and assigning an earlier entry.
Bollinger squeeze breakout
Define bandwidth in consistent units: (upper − lower) / middle is a ratio; multiplying by 100 gives percent. Define the prior squeeze window and the later price breakout separately. ADX cannot simultaneously be below 20 and above 25 on the same bar. Returning inside the outer band is also different from crossing the middle band.
Preserve a held-out period, include failed and untriggered setups, and model fees, spread, slippage and gaps. Report trade count, net results and drawdown with the rules and data source. A higher win rate can coexist with worse net returns.
ADX, DMI and RSI measure different transformations
+DI and -DI retain the directional sides of high/low movement. ADX smooths their relative separation after removing the sign. RSI compares smoothed positive and negative close changes. They do not have universal daily signal counts or a fixed ranking of lag. Combining them adds conditions that need their own evaluation; it does not automatically eliminate false signals.
For a custom script, specify whether to use TradingView's built-in DMI or the retained formula, the two lengths, input bounds, missing-data policy and completed-bar behavior. Pineify's AI Coding Agent can draft and revise that specification. Review the generated source and numerical output; product access and usage depend on your plan.
Build an ADX specificationADX questions
Sources and related indicators
- TradingView: ADX calculation and interpretation
- TradingView: ta.dmi reference
- TradingView: open-bar updates and repainting
- TradingView: Pine Editor access
- DMI indicator
- RSI indicator
- Bollinger Bands
Calculation examples are synthetic. The page reports no personal trading experience, benchmark or measured strategy return.