Macro Directional Index
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © jordanfray

//@version=5

//  About This Indicator
//  The default settings for this indicator are for BTC/USDT and intended to be used on the 3D timeframe to identify market trends. 
//  This indicator does a great job identifying whether the market is bullish, bearish, or consolidating. 
//  This can also work well on lower time frames to help identify when a trend is strong or when it's reversing. 

indicator("Simplified Macro Directional Index", shorttitle="Macro Directional Index")
import jordanfray/threengine_global_automation_library/89 as bot

//  Colors - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
green                               = color.new(#2DBD85,0)
lightGreen                          = color.new(#2DBD85,70)
red                                 = color.new(#E02A4A,0)
lightRed                            = color.new(#E02A4A,70)
purple                              = color.new(#5A00FF,0)
black                               = color.new(#000000,0)
lightBlack                          = color.new(#000000,90)

//  Tooltips
diDistanceRocLengthTip              = "Core to this indicator is the rate at which DI+ and DI- are moving away or towards each other. This is called The Rate of Change (ROC)." + "\n \n"
                                      + "The ROC length dictates how many bars back you want to compare to the current bar to see how much it has changed." + "\n \n"
                                      + "It is calculated like this:" + "\n"
                                      + "(source - source[length]/source[length]) * 100" + "\n \n"
                                      + "This indicator has 4 values in the status line: " + "\n"
                                      + "    1: DI+" + "\n"
                                      + "    2: DI-" + "\n"
                                      + "    3: Distance between DI+ and DI-" + "\n"
                                      + "    4: DI Rate of Change" + "\n \n"
                                      + "Default: 1"

diDistanceRocEmaLengthTip           = "The rate of change is smoothed using an EMA. A shorter EMA length will cause the ROC to flip back and forth between positive and negative while a larger EMA length will cause the ROC to change less often." + "\n \n"
                                      + "Since the rate of change is used to indicate periods of 'consolidation', you want to find a setting that doesn't flip back and forth too often." + "\n \n"
                                      + "Default: 5"
                                      
diMidlineChannelWidthTip            = "Between the DI+ and DI- is a black centerline. Offset from this centerline is a channel that is used to filter out false crosses of the DI+ and DI-. "
                                      + "Sometimes, the DI+ and DI- lines will come together in this channel and cross momentarily before resuming the direction prior to the cross. "
                                      + "When this happens, you don't want to flip your bias too soon. The wider the channel, the later the indicator will signal a DI reversal. A narrower channel will call it sooner but risks being more choppy and indicating a false cross." + "\n \n"
                                      + "Default: 5"

//  Indicator Settings
diLength    		                = input.int(defval=23, minval=1, step=1, title="DI Length", group="Directional Index (DI)")
diDistanceRocLength                 = input.int(defval=1, title="DI Distance ROC Length", group="Directional Index (DI)", tooltip=diDistanceRocLengthTip)
diDistanceRocEmaLength              = input.int(defval=5, title="DI Distance ROC Smoothing", group="Directional Index (DI)", tooltip=diDistanceRocEmaLengthTip)
diMidlineChannelWidth               = input.float(defval=12, title="DI Midline Channel Width (%)", group="Directional Index (DI)", tooltip=diMidlineChannelWidthTip)

//  Directional Index
TrueRange          	                = math.max(math.max(high-low, math.abs(high-nz(close[1]))), math.abs(low-nz(close[1])))
DMPlus             	                = high-nz(high[1]) > nz(low[1])-low ? math.max(high-nz(high[1]), 0): 0
DMMinus            	                = nz(low[1])-low > high-nz(high[1]) ? math.max(nz(low[1])-low, 0): 0
SmoothedTR         	                = 0.0
SmoothedTR         	                := nz(SmoothedTR[1]) - (nz(SmoothedTR[1])/diLength) + TrueRange
SmoothedDMPlus     	                = 0.0
SmoothedDMPlus     	                := nz(SmoothedDMPlus[1]) - (nz(SmoothedDMPlus[1])/diLength) + DMPlus
SmoothedDMMinus    	                = 0.0
SmoothedDMMinus    	                := nz(SmoothedDMMinus[1]) - (nz(SmoothedDMMinus[1])/diLength) + DMMinus
diPlus             	                = SmoothedDMPlus / SmoothedTR * 100
diMinus            	                = SmoothedDMMinus / SmoothedTR * 100

distanceBetweenDi  	                = (diPlus - diMinus) < 0 ? (diPlus - diMinus)*-1 : (diPlus - diMinus)
distanceBetweenDiRoc                = bot.getRateOfChange(distanceBetweenDi, diDistanceRocLength)
distanceBetweenDiRocEma             = bot.taGetEma("EMA", distanceBetweenDiRoc, diDistanceRocEmaLength)

diCenterLine                        = (diPlus + diMinus)/2
diCenterLineChannelTop              = diCenterLine + (diCenterLine*(diMidlineChannelWidth/100))
diCenterLineChannelBottom           = diCenterLine - (diCenterLine*(diMidlineChannelWidth/100))

bool diDistanceRocEmaTrendingDown   = distanceBetweenDiRocEma < 0
bool diPlusAboveDiMinus             = diPlus > diMinus
bool diPlusAboveCenterChannel       = diPlus > diCenterLineChannelTop
bool diMinusAboveDiPlus             = diMinus > diPlus
bool diMinusAboveCenterChannel      = diMinus > diCenterLineChannelTop

bool bullMarket                     = diPlusAboveDiMinus
bool bearMarket                     = diMinusAboveDiPlus

bool bullishConsolidation           = diPlusAboveDiMinus and diPlusAboveCenterChannel and diDistanceRocEmaTrendingDown
bool bearishConsolidation           = diMinusAboveDiPlus and diPlusAboveCenterChannel and diDistanceRocEmaTrendingDown

var string macroMarketStatus        = na

if diPlusAboveDiMinus and diPlusAboveCenterChannel
    macroMarketStatus               := "bullish"
else
    if diMinusAboveDiPlus and diMinusAboveCenterChannel
        macroMarketStatus           := "bearish"

//  Plots
diPlusPlot			                = plot(series=diPlus, title="DI+", color=green, linewidth=2, editable=false, display=display.pane + display.status_line)
diMinusPlot			                = plot(series=diMinus, title="DI-", color=red, linewidth=2, editable=false, display=display.pane + display.status_line)
diDistancePlot		                = plot(series=distanceBetweenDi, title="Distance Between DI", color=purple, editable=false, display=display.status_line)
diDistanceRocPlot		            = plot(series=distanceBetweenDiRocEma, title="DI Distance Rate of Change", color=purple, editable=false, display=display.status_line)

diCenterLineChannelTopPlot          = plot(series=diCenterLineChannelTop, title="DI Center Channel Top", color=black, editable=false, display=display.none)
diCenterLinePlot                    = plot(series=diCenterLine, title="DI Center Line", color=black, editable=false, linewidth=2, display=display.pane)
diCenterLineChannelBottomPlot       = plot(series=diCenterLineChannelBottom, title="DI Center Channel Bottom", color=black, editable=false, display=display.none)

bgcolor(macroMarketStatus == "bullish" ? lightGreen : lightRed, editable = false, title = "Background Colors (Market Status)")
fill(diCenterLineChannelTopPlot, diCenterLineChannelBottomPlot, editable = false, title = "DI Center Channel", color = lightBlack)

//  Alert Conditions
alertcondition(condition = diPlusAboveDiMinus and diPlusAboveCenterChannel, title = "Macro DI - Long", message = "Directional Index Plus crossed above the center channel.")
alertcondition(condition = diMinusAboveDiPlus and diMinusAboveCenterChannel, title = "Macro DI - Short", message = "Directional Index Minus crossed above the center channel.")