Average Directional Index (ADX)

Haiyue
12min

I. What is ADX

The Average Directional Index (ADX) is a trend strength indicator introduced by American technical analysis pioneer J. Welles Wilder Jr. in 1978 in his book New Concepts in Technical Trading Systems.

ADX belongs to the Trend Strength class of indicators and is used to measure how strong or weak the current market trend is. ADX is typically used together with two auxiliary lines, +DI (Positive Directional Indicator) and -DI (Negative Directional Indicator). Together, these three form the DMI system (Directional Movement Index).

The core design philosophy of ADX is: first determine the directional bias of price through Directional Movement, then apply successive layers of smoothing to produce a stable trend strength reading. The default period is n=14n = 14.

Tip

ADX itself does not indicate trend direction — it only measures trend strength. An ADX of 40 could represent a strong uptrend or a strong downtrend. You must combine it with the +DI and -DI relationship to determine direction.


II. Mathematical Principles and Calculation

2.1 Directional Movement (DM)

+DM (Positive Directional Movement):

+DMt={HtHt1if (HtHt1)>(Lt1Lt) and (HtHt1)>00otherwise+\text{DM}_t = \begin{cases} H_t - H_{t-1} & \text{if } (H_t - H_{t-1}) > (L_{t-1} - L_t) \text{ and } (H_t - H_{t-1}) > 0 \\ 0 & \text{otherwise} \end{cases}

-DM (Negative Directional Movement):

DMt={Lt1Ltif (Lt1Lt)>(HtHt1) and (Lt1Lt)>00otherwise-\text{DM}_t = \begin{cases} L_{t-1} - L_t & \text{if } (L_{t-1} - L_t) > (H_t - H_{t-1}) \text{ and } (L_{t-1} - L_t) > 0 \\ 0 & \text{otherwise} \end{cases}
Note

On any given bar, at most one of +DM and -DM is positive; the other is 0. If the two are equal, both are 0. This ensures each bar is classified into only one direction.

2.2 Wilder’s Smoothing

Apply Wilder’s Smoothing (period nn) separately to +DM, -DM, and TR:

Initial value (sum of the first nn periods):

Smoothedn=i=1nXi\text{Smoothed}_n = \sum_{i=1}^{n} X_i

Subsequent recursive calculation:

Smoothedt=Smoothedt1Smoothedt1n+Xt\text{Smoothed}_t = \text{Smoothed}_{t-1} - \frac{\text{Smoothed}_{t-1}}{n} + X_t

2.3 Directional Indicators (+DI / -DI)

+DIt=100×Smoothed(+DM)tSmoothed(TR)t+\text{DI}_t = 100 \times \frac{\text{Smoothed}(+\text{DM})_t}{\text{Smoothed}(\text{TR})_t} DIt=100×Smoothed(DM)tSmoothed(TR)t-\text{DI}_t = 100 \times \frac{\text{Smoothed}(-\text{DM})_t}{\text{Smoothed}(\text{TR})_t}

2.4 Directional Index (DX) and ADX

DX (Directional Index):

DXt=100×+DIt(DIt)+DIt+(DIt)\text{DX}_t = 100 \times \frac{|+\text{DI}_t - (-\text{DI}_t)|}{+\text{DI}_t + (-\text{DI}_t)}

ADX (Average Directional Index):

  • The first ADX = simple average of the first nn DX values
  • Subsequent values:
ADXt=ADXt1×(n1)+DXtn\text{ADX}_t = \frac{\text{ADX}_{t-1} \times (n - 1) + \text{DX}_t}{n}

2.5 Calculation Steps Summary

  1. Calculate +DM, -DM, and TR for each bar
  2. Apply Wilder’s Smoothing to +DM, -DM, and TR separately (period 14)
  3. Calculate +DI and -DI
  4. Calculate DX
  5. Apply Wilder’s Smoothing to DX to obtain ADX
Note

Because ADX requires an additional round of smoothing on DX, it needs at least 2n1=272n - 1 = 27 bars to produce the first valid value.


III. Python Implementation

import numpy as np
import pandas as pd

def adx(high: pd.Series, low: pd.Series, close: pd.Series,
        period: int = 14) -> pd.DataFrame:
    """
    Calculate the ADX / +DI / -DI indicators.

    Parameters:
        high   : Series of high prices
        low    : Series of low prices
        close  : Series of closing prices
        period : Smoothing period, default 14

    Returns:
        DataFrame containing plus_di, minus_di, adx columns
    """
    n = len(close)

    # Calculate True Range
    prev_close = close.shift(1)
    tr1 = high - low
    tr2 = (high - prev_close).abs()
    tr3 = (low - prev_close).abs()
    tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)

    # Calculate +DM and -DM
    up_move = high - high.shift(1)
    down_move = low.shift(1) - low

    plus_dm = pd.Series(0.0, index=close.index)
    minus_dm = pd.Series(0.0, index=close.index)

    # +DM: take value when upward movement is positive and greater than downward movement
    plus_dm[(up_move > down_move) & (up_move > 0)] = up_move
    # -DM: take value when downward movement is positive and greater than upward movement
    minus_dm[(down_move > up_move) & (down_move > 0)] = down_move

    # Wilder's Smoothing
    smoothed_tr = pd.Series(np.nan, index=close.index)
    smoothed_plus_dm = pd.Series(np.nan, index=close.index)
    smoothed_minus_dm = pd.Series(np.nan, index=close.index)

    # Initial values: sum of the first 'period' valid values
    smoothed_tr.iloc[period] = tr.iloc[1:period + 1].sum()
    smoothed_plus_dm.iloc[period] = plus_dm.iloc[1:period + 1].sum()
    smoothed_minus_dm.iloc[period] = minus_dm.iloc[1:period + 1].sum()

    for i in range(period + 1, n):
        smoothed_tr.iloc[i] = smoothed_tr.iloc[i-1] - smoothed_tr.iloc[i-1] / period + tr.iloc[i]
        smoothed_plus_dm.iloc[i] = smoothed_plus_dm.iloc[i-1] - smoothed_plus_dm.iloc[i-1] / period + plus_dm.iloc[i]
        smoothed_minus_dm.iloc[i] = smoothed_minus_dm.iloc[i-1] - smoothed_minus_dm.iloc[i-1] / period + minus_dm.iloc[i]

    # +DI and -DI
    plus_di = 100 * smoothed_plus_dm / smoothed_tr
    minus_di = 100 * smoothed_minus_dm / smoothed_tr

    # DX
    dx = 100 * (plus_di - minus_di).abs() / (plus_di + minus_di)

    # ADX: apply Wilder's Smoothing to DX
    adx_values = pd.Series(np.nan, index=close.index)
    # First ADX = mean of 'period' DX values starting from index 'period'
    first_adx_start = period + period  # need 'period' DX values
    if first_adx_start < n:
        adx_values.iloc[first_adx_start] = dx.iloc[period + 1:first_adx_start + 1].mean()
        for i in range(first_adx_start + 1, n):
            adx_values.iloc[i] = (adx_values.iloc[i-1] * (period - 1) + dx.iloc[i]) / period

    return pd.DataFrame({
        'plus_di': plus_di,
        'minus_di': minus_di,
        'dx': dx,
        'adx': adx_values
    })


# ============ Usage Example ============
if __name__ == '__main__':
    np.random.seed(42)
    n_days = 120

    # Generate simulated OHLCV data with trend phases
    trend = np.concatenate([
        np.linspace(0, 5, 40),     # Uptrend
        np.linspace(5, 5, 40),     # Sideways
        np.linspace(5, -2, 40)     # Downtrend
    ])
    noise = np.cumsum(np.random.randn(n_days) * 0.3)
    base_price = 100 + trend + noise

    df = pd.DataFrame({
        'open':   base_price + np.random.randn(n_days) * 0.2,
        'high':   base_price + np.abs(np.random.randn(n_days) * 0.6),
        'low':    base_price - np.abs(np.random.randn(n_days) * 0.6),
        'close':  base_price,
        'volume': np.random.randint(1000, 10000, n_days)
    })

    # Calculate ADX
    result = adx(df['high'], df['low'], df['close'], period=14)
    df = pd.concat([df, result], axis=1)

    print("=== ADX Results (Last 10 Rows) ===")
    print(df[['close', 'plus_di', 'minus_di', 'adx']].tail(10).round(2).to_string())

    # Trend strength assessment
    latest = df.dropna(subset=['adx']).iloc[-1]
    adx_val = latest['adx']
    if adx_val > 25:
        direction = "uptrend" if latest['plus_di'] > latest['minus_di'] else "downtrend"
        print(f"\nCurrent ADX = {adx_val:.2f}, market is in a {direction}")
    else:
        print(f"\nCurrent ADX = {adx_val:.2f}, market is in a range-bound / trendless state")

IV. Problems the Indicator Solves

4.1 Trend Strength Assessment

The most essential function of ADX is to answer: “Is there a trend in the market right now? How strong is it?”

ADX ValueTrend StrengthSuggested Strategy
0 — 20No trend or very weak trendUse oscillator strategies (mean reversion)
20 — 25Trend may be formingWatch closely, prepare to enter
25 — 50Confirmed trendUse trend-following strategies
50 — 75Strong trendHold trend positions
75 — 100Extreme trend (rare)Watch for potential trend reversal

4.2 Trend Direction

Crossovers of +DI and -DI provide directional signals:

  • +DI crosses above -DI: bullish signal, upward directional movement dominates
  • -DI crosses above +DI: bearish signal, downward directional movement dominates
Warning

+DI/-DI crossover signals generate frequent false signals in range-bound markets. It is recommended to execute crossover signals only when ADX > 20 or ADX > 25, filtering out low-quality trades.

4.3 Strategy Selection Filter

ADX helps traders choose between trend-following and mean-reversion strategies:

  • ADX > 25: activate trend-following strategies (e.g., moving average crossovers, breakouts)
  • ADX < 20: activate oscillator strategies (e.g., RSI overbought/oversold, Bollinger Band reversion)

4.4 Typical Trading Strategies

  1. DI Crossover Strategy: go long when +DI crosses above -DI and ADX > 25; go short when -DI crosses above +DI and ADX > 25
  2. ADX Breakout Strategy: when ADX rises from below 20 to above 25, enter in the direction of the DI lines
  3. ADX Pullback Strategy: when ADX declines from elevated levels, the trend may be ending — prepare to reverse or exit
  4. ADXR Crossover Strategy: ADXR = (current ADX + ADX from nn periods ago) / 2; when ADX crosses above ADXR, the trend is strengthening

V. Advantages, Disadvantages, and Use Cases

Advantages

AdvantageDescription
Quantifies trend strengthProvides a clear 0—100 reading that is programmable and backtestable
Separates direction from strengthADX measures strength, +DI/-DI provide direction — clear separation of concerns
Double smoothingTwo layers of Wilder’s Smoothing produce very stable output
Strategy selectorHelps decide whether to use trend-following or mean-reversion strategies

Disadvantages

DisadvantageDescription
Significant lagDouble smoothing causes ADX to react slowly to trend changes
Long warm-up periodRequires at least 27 bars to produce the first ADX value
Not suitable as a standalone systemADX works best as a filter; needs a separate entry signal
ADX decline does not equal trend reversalA falling ADX only means the trend is weakening, not reversing

Use Cases

  • Best suited for: determining whether a trend exists, filtering false signals in range-bound markets
  • Well suited for: combining with other trend indicators (moving averages, MACD)
  • Not suited for: use as a standalone entry/exit signal for short-term trading

Comparison with Similar Indicators

IndicatorWhat It MeasuresCharacteristics
ADXTrend strength (not direction)The classic trend strength indicator; lagging but stable
AroonTrend direction and strengthFaster response; based on position of highs/lows
VortexTrend directionBased on vortex movement concept; simpler calculation
Parabolic SARTrend direction and stop-lossProvides direct stop-loss levels; does not quantify strength
Tip

Parameter Tuning Recommendations: Wilder’s original 14-period setting performs well across most time frames. Shortening the period (e.g., 7) makes ADX more responsive but increases noise; lengthening the period (e.g., 21) provides smoother output but more lag. In practice, using ADX as a “switch” or “filter” rather than as a standalone trading signal is the most effective approach.