Directional Movement Indicator (DMI and ADX) - Tartigradia
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © BeikabuOyaji
// This is an extended indicator, from the original one by BeikabuOyaji: https://www.tradingview.com/script/VTPMMOrx-ADX-and-DI/
// extended by tartigradia (plot DMX, added second threshold as per Welles Wilder original indicator, added background highlighting depending on ADX and thresholds to ease interpretation of trends vs ranging market, converted to pinescript v5, changed color scheme to work on black theme)
// for more infos on how to use this indicator, read (in french): https://www.leguideboursier.com/apprendre-bourse/analyse-technique/indicateur-adx.php and https://www.leguideboursier.com/apprendre-bourse/analyse-technique/dmi-indicateur.php
//
// Description: Direction Movement Indicator (DMI) is a trend indicator invented by J. Welles Wilder, who also authored RSI.
// DMI+ and DMI- respectively indicate pressure towards bullish or bearish trends.
// ADX is the average directional movement, which indicates whether the market is currently trending (high values above 25) or ranging (below 20) or undecided (between 20 and 25).
// DMX is the non smoothed ADX, which allows to detect transitions from trending to ranging markets and inversely with zero lag, but at the expense of having much more noise.
//
// Usage: To use this indicator for entry: when DMI+ crosses over DMI-, there is a bullish sentiment, however ADX also needs to be above 25 to be significant, otherwise the market is not trending.
// When ADX is below 20, the market is ranging (no trend), and when ADX is between 20 and 25, the market is undecided (we don't know if it's trending or ranging).
// Inversely, when DMI+ crosses under DMI- and ADX is above 25, then the sentiment is significantly bearish, but if ADX is below 20, the signal should be disregarded, since market is ranging.
// This indicator automatically highlights the background in purple when ADX is above 25 to indicate that there is a trend, regardless of its direction, and in dark blue when ADX is below 20, indicating that the market is ranging. When the market is undecided, the background is not highlighted.
// There is a "Trend bar" at the bottom that allows for easy interpretation:
// * it is colored in green when the market is bullishly trending (DMI+ over DMI- and ADX > 25).
// * in red when market is bearishly trending (DMI+ under DMI- and ADX > 25).
// * in dark blue when market is ranging (ADX < 20).
// * in gray when market is undecided (20 <= ADX < 25).
// To make trend changes signals appear faster, the default SMA calculations embedded inside the ADX and DMI calculations are replaceable by other types of moving averages. By default, a Zero-Lag Moving Average (ZLMA) is selected, as it provides a smooth yet very reactive signal.
// To get the most reactive signal, smoothing can be disabled by highlighting the background and trend bar using DMX, which is the non-smoothed ADX, and is hence much more reactive but also much more noisy (disabled by default).
// Finally, arrows can be activated in the Style menu to automatically show when the two conditions described above are met, or these can be used in a strategy.
// By default, this indicator is set to always calculate on the weekly timeframe, as this is when the signals appear to be the most reliable. The timeframe can however be changed in the parameters, and can be set to "Chart" to adapt to currently user selected timeframe in TradingView.

//@version=5
indicator('Directional Movement Indicator (DMI and ADX) - Tartigradia', shorttitle='ADX_DMI', timeframe="W", timeframe_gaps=false)

// AUX FUNCTIONS
//
zema(src, len) =>
    // Zero-lag EMA
    // by DreamsDefined in: https://www.tradingview.com/script/pFyPlM8z-Reverse-Engineered-RSI-Key-Levels-MTF/
    ema1 = ta.ema(src, len)
    ema2 = ta.ema(ema1, len)
    d = ema1 - ema2
    zlema = ema1 + d
    zlema

approximate_sma(x, ma_length) =>
    // Approximate SMA, which substracts the average instead of popping the oldest element
    // This is the original SMA used in this indicator
    // For some reason, this appears to give the same results as a standard RMA
    sma = 0.0
    sma := nz(sma[1]) - (nz(sma[1] / ma_length)) + x
    sma

f_maSelect(serie, ma_type="sma", ma_length=14) =>
    switch ma_type
        "sma" => ta.sma(serie, ma_length)
        "ema" => ta.ema(serie, ma_length)
        "rma" => ta.rma(serie, ma_length)
        "wma" => ta.wma(serie, ma_length)
        "vwma" => ta.vwma(serie, ma_length)
        "swma" => ta.swma(serie)
        "hma" => ta.hma(serie, ma_length)
        "alma" => ta.alma(serie, ma_length, 0.85, 6)
        "zlma" => zema(serie, ma_length)
        "approximate_sma" => approximate_sma(serie, ma_length)
        =>
            runtime.error("No matching MA type found.")
            float(na)

detect_switch(series bool x) =>
    // Detects when a series turn from false to true, this is useful to detect abrupt transitions and mark them with plotshape
    x and not x[1]

// PARAMETERS
//
ma_length_dmi = input.int(14, title='Smoothing length for DMI+ and DMI-', minval=1, step=1, maxval=300)
ma_type_dmi = input.string('approximate_sma', 'Moving Average Type for DMI+ and DMI-', options=['sma', 'ema', 'rma', 'wma', 'vwma', 'swma', 'hma', 'alma', 'zlma', 'approximate_sma'], tooltip='What kind of smoothing algorithm do we apply on the signals? For the original DMI+ and DMI- as defined by Welles Wilder, set approximate_sma.')
th = input.float(20, title='ADX No trend threshold', step=1.0)
th2 = input.float(25, title='ADX trend threshold', step=1.0)
ma_length_adx = input.int(14, "SMA Length for ADX", minval=1, maxval=300, step=1)
ma_type_adx = input.string('zlma', 'Moving Average Type for ADX', options=['sma', 'ema', 'rma', 'wma', 'vwma', 'swma', 'hma', 'alma', 'zlma', 'approximate_sma'], tooltip='What algorithm to use to smooth out the signals? Default: zlma, as it provides the most reactive signal while still being smooth (less noisy, less false positives). For the original ADX as defined by Welles Wilder, select sma.')
bg_highlight = input.bool(true, title='Highlight background depending on ADX thresholds?', tooltip='Highlight background when ADX is below the no trend threshold (ranging market) or above the trend threshold (trending market). Between both thresholds, the market is undecided.')
show_trend_bar = input.bool(true, title='Show trend bar based on DMI+/DMI- crossover direction with ADX filtering?', tooltip='Display a bar with color indicating trend direction or lack of trend: bullish if DMI+ is above DMI- and ADX > trend threshold (default green), bearish if DMI+ is below DMI- and ADX > trend threshold (default red), otherwise if ADX < no-trend threshold then there is no trend (default orange) and if ADX is between the no-trend threshold and the trend threshold then the market is uncertain whether it is trending or ranging (default gray).')
bg_highlight_dmx = input.bool(false, title='Use DMX (non-smoothed ADX) instead of ADX in calculations to highlight background and trend bar?', tooltip='This allows to experience no lag in trend changes, but there is much more noise than with the smoothed ADX, hence lots of false positives.')

// CALCULATIONS
//
TrueRange = math.max(math.max(high - low, math.abs(high - nz(close[1]))), math.abs(low - nz(close[1])))
DirectionalMovementPlus = high - nz(high[1]) > nz(low[1]) - low ? math.max(high - nz(high[1]), 0) : 0
DirectionalMovementMinus = nz(low[1]) - low > high - nz(high[1]) ? math.max(nz(low[1]) - low, 0) : 0

SmoothedTrueRange = f_maSelect(TrueRange, ma_type_dmi, ma_length_dmi)
SmoothedDirectionalMovementPlus = f_maSelect(DirectionalMovementPlus, ma_type_dmi, ma_length_dmi)
SmoothedDirectionalMovementMinus = f_maSelect(DirectionalMovementMinus, ma_type_dmi, ma_length_dmi)

DMIPlus = SmoothedDirectionalMovementPlus / SmoothedTrueRange * 100
DMIMinus = SmoothedDirectionalMovementMinus / SmoothedTrueRange * 100
DMX = math.abs(DMIPlus - DMIMinus) / (DMIPlus + DMIMinus) * 100
ADX = f_maSelect(DMX, ma_type_adx, ma_length_adx)

// PLOTS
//
plot(DMIPlus, color=color.new(color.green, 0), title='DI+')
plot(DMIMinus, color=color.new(color.red, 0), title='DI-')
plot(ADX, color=color.new(color.white, 0), title='ADX', linewidth=2)
plot(DMX, color=color.new(color.gray, 0), title='DMX')
hline(th, color=color.new(color.white, 50), title="ADX no trend threshold level")
hline(th2, color=color.new(color.white, 50), title="ADX trend threshold level")

// Background highlight of trends
trend_val = bg_highlight_dmx ? DMX : ADX
bgcolor(not bg_highlight ? na : trend_val < th ? color.new(color.blue, 70) : trend_val >= th2 ? color.new(color.purple, 70) : na, title='Background highlight of trend existence according to ADX or DMX (not direction)')
trendbar1 = hline(0.0, title='Trend bar top line', linewidth=1, color=color.new(color.white, 100)) // use invisible color lines instead of display.none to ensure chart is scaled to include bar, otherwise with display.none the fill will still be displayed but the chart won't necessarily get scaled to display it
trendbar2 = hline(-5.0, title='Trend bar bottom line', linewidth=1, color=color.new(color.white, 100))
fill(trendbar1, trendbar2, color= not show_trend_bar ? na :
                             trend_val < th ? color.blue :
                             trend_val >= th and trend_val < th2 ? color.gray :
                                 trend_val >= th2 and DMIPlus > DMIMinus ? color.green :
                                     trend_val >= th2 and DMIPlus < DMIMinus ? color.red : na
     , title='Trend bar (DMI+/DMI- crossover combined with ADX filtering)')

// Plot abrupt changes as arrows
dmibullish = DMIPlus > DMIMinus and trend_val >= th2
dmibearish = DMIPlus < DMIMinus and trend_val >= th2
trend_val_unsignificant = trend_val < th2

//plotarrow(detect_switch(dmibullish) ? 1 : detect_switch(dmibearish) ? -1 : na, title="DMI+ crosses DMI-", display=display.none)  // TODO: display scaling is totally off for some unknown reason
plotshape(detect_switch(dmibullish), title='Signal: DMI+ crossover DMI-', style=shape.triangleup, location=location.top, color=color.new(color.green, 0), size=size.auto, display=display.pane)
plotshape(detect_switch(dmibearish), title='Signal: DMI+ crossunder DMI-', style=shape.triangledown, location=location.top, color=color.new(color.red, 0), size=size.auto, display=display.pane)
plotshape(detect_switch(trend_val_unsignificant), title='Signal: Trend disappears', style=shape.xcross, location=location.top, color=color.new(color.gray, 0), size=size.auto, display=display.pane)
