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.

Reading a calculation without turning it into a return forecast
ObservationSupported interpretationNot established
ADX exceeds 25A conventional strength reference has been exceededTrade profitability or bullish direction
ADX is below 20Smoothed relative directional imbalance is lowThat all trend strategies must lose
ADX falls while price risesThe smoothed imbalance is decreasingA scheduled price reversal
ADX exceeds 50A high reading relative to its 0–100 scaleExhaustion 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.

Pine Script v6
// 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)
Retained chart illustration for the ADX indicator reference
Existing chart illustration. Verify values using the source and the same instrument, session and interval; an image alone is not a performance record.

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.

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

  1. 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.
  2. Record +DI, -DI, DX and ADX where available. Check outside bars, equal high/low expansions, zero directional movement and initial unavailable values.
  3. Compare the two examples after warmup; investigate any difference in data initialization or missing-value handling before attributing it to signal quality.
  4. 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 specification

ADX questions

ADX smooths the absolute relative difference between +DI and -DI. It describes directional imbalance on a 0–100 scale when the inputs are valid. Swapping the two DI values leaves DX unchanged, so ADX alone cannot distinguish an uptrend from a downtrend.

It means the selected calculation has crossed a conventional reference level. TradingView describes 20 and 25 as weak/strong trend reference levels. Neither value establishes an expected return, a universal entry threshold or a probability of success.

There is no verified best combination on this page. Both examples start at 14 for DI length and ADX smoothing. These are two separate smoothing stages. Compare alternatives on the same data, preserve an untouched evaluation period and include costs before judging a trading rule.

The first retained custom script calls p_ta_adx with fixed values 14 and 14; it has no length inputs. The second built-in ta.dmi example exposes both lengths in Inputs. Use positive integer values; the example input declarations do not enforce a positive minimum.

Use the documented ta.dmi function to obtain the three series +DI, -DI and ADX. The custom p_ta_adx function on this page is separately defined in the script. A name beginning with p_ta is not a TradingView built-in.

The calculation can operate on available OHLC bars across markets and intervals. That does not establish that one interval is more profitable or that crypto requires four-hour bars. Check session coverage, gaps, bar construction and the intended trading rules separately.

No. It means the smoothed directional imbalance has decreased. Price may continue in the same direction, consolidate or reverse. A reversal claim requires a separate price condition and a defined evaluation horizon.

They plot indicators. Neither example submits orders, tests returns or defines alert conditions. The open bar can change the values, so a later signal implementation must specify confirmation timing and alert frequency.

Sources and related indicators

Calculation examples are synthetic. The page reports no personal trading experience, benchmark or measured strategy return.