Median Absolute Deviation Outlier Fractals
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © CryptoGearBox

//@version=5
indicator("Median Absolute Deviation Fractals", shorttitle = "MAD Fractals", overlay = true)
// ----------------------------------------------------------------------
// Inputs
// ----------------------------------------------------------------------
i_src = input.source(close, "Source")
i_baseline = input.int(89, "Baseline Length")
i_madLen = input.int(13, "MAD Length")
i_madDeviations = input.float(2.9652, "MAD Deviations Limit")
i_showRegFrac = input.bool(true, "Show Regular Fractals?")

// ----------------------------------------------------------------------
// Functions
// ----------------------------------------------------------------------
// G-Channels as baseline for trend
// Source: https://www.tradingview.com/script/fIvlS64B-G-Channels-Efficient-Calculation-Of-Upper-Lower-Extremities/
f_baseline(_src, _length) =>
    float _gTop = 0.
    float _gBot = 0.
    _gTop := math.max(_src, nz(_gTop[1])) - nz(_gTop[1] - _gBot[1]) / _length
    _gBot := math.min(_src, nz(_gBot[1])) + nz(_gTop[1] - _gBot[1]) / _length
    float _baseline = math.avg(_gTop, _gBot)

// ----------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------
// Trend
float baseline = f_baseline(i_src, i_baseline)

// Bill William's Fractals
int n = 2
bool upFractal = ((high[n+2]  < high[n]) and (high[n+1]  < high[n]) and (high[n-1] < high[n]) and (high[n-2] < high[n]))
     or ((high[n+3]  < high[n]) and (high[n+2]  < high[n]) and (high[n+1] == high[n]) and (high[n-1] < high[n]) and (high[n-2] < high[n]))
     or ((high[n+4]  < high[n]) and (high[n+3]  < high[n]) and (high[n+2] == high[n]) and (high[n+1] <= high[n]) and (high[n-1] < high[n]) and (high[n-2] < high[n]))
     or ((high[n+5] < high[n]) and (high[n+4]  < high[n]) and (high[n+3] == high[n]) and (high[n+2] == high[n]) and (high[n+1] <= high[n]) and (high[n-1] < high[n]) and (high[n-2] < high[n]))
     or ((high[n+6] < high[n]) and (high[n+5] < high[n]) and (high[n+4] == high[n]) and (high[n+3] <= high[n]) and (high[n+2] == high[n]) and (high[n+1] <= high[n]) and (high[n-1] < high[n]) and (high[n-2] < high[n]))

bool dnFractal = ((low[n+2]  > low[n]) and (low[n+1]  > low[n]) and (low[n-1] > low[n]) and (low[n-2] > low[n]))
     or ((low[n+3]  > low[n]) and (low[n+2]  > low[n]) and (low[n+1] == low[n]) and (low[n-1] > low[n]) and (low[n-2] > low[n]))
     or ((low[n+4]  > low[n]) and (low[n+3]  > low[n]) and (low[n+2] == low[n]) and (low[n+1] >= low[n]) and (low[n-1] > low[n]) and (low[n-2] > low[n]))
     or ((low[n+5] > low[n]) and (low[n+4]  > low[n]) and (low[n+3] == low[n]) and (low[n+2] == low[n]) and (low[n+1] >= low[n]) and (low[n-1] > low[n]) and (low[n-2] > low[n]))
     or ((low[n+6] > low[n]) and (low[n+5] > low[n]) and (low[n+4] == low[n]) and (low[n+3] >= low[n]) and (low[n+2] == low[n]) and (low[n+1] >= low[n]) and (low[n-1] > low[n]) and (low[n-2] > low[n]))

float upFracLine = 0.
float dnFracLine = 0.
upFracLine := upFractal ? high[2] : upFracLine[1]
dnFracLine := dnFractal ? low[2] : dnFracLine[1]

// MAD Fractals
bool dnStart = close[1] > nz(baseline[1]) and close < baseline
bool upStart = close[1] < nz(baseline[1]) and close > baseline
var int trendDir = 1
var int dirCount = 0
if trendDir == 1 and dnStart
    trendDir := -1
    dirCount := 0
if trendDir == -1 and upStart
    trendDir := 1
    dirCount := 0
dirCount := dirCount + 1
float median = ta.percentile_nearest_rank(ta.tr, i_madLen, 50)
float mad = ta.percentile_nearest_rank(math.abs(ta.tr - median), i_madLen, 50)
float threshold = median + mad * i_madDeviations
bool outlier = ta.tr >= threshold
bool upOutlierFrac = outlier[2] and upFractal and trendDir[2] == -1
bool dnOutlierFrac = outlier[2] and dnFractal and trendDir[2] == 1
float upMadFracLine = 0.
float dnMadFracLine = 0.
upMadFracLine := upOutlierFrac ? high[2] : high[2] > upMadFracLine[1] ? na : upMadFracLine[1]
dnMadFracLine := dnOutlierFrac ? low[2] : low[2] < dnMadFracLine[1] ? na : dnMadFracLine[1]

// ----------------------------------------------------------------------
// Plot
// ----------------------------------------------------------------------
plot(i_showRegFrac ? upFracLine : na, title = "Up Fractal Entry", color = color.new(color.lime, 50), linewidth = 1, style = plot.style_circles, offset = -2)
plot(i_showRegFrac ? dnFracLine : na, title = "Dn Fractal Entry", color = color.new(color.red, 50), linewidth = 1, style = plot.style_circles, offset = -2)
plot(upMadFracLine, title = "Up MAD Fractal", color = color.new(color.fuchsia, 50), linewidth = 2, style = plot.style_circles, offset = -2)
plot(dnMadFracLine, title = "Dn MAD Fractal", color = color.new(color.fuchsia, 50), linewidth = 2, style = plot.style_circles, offset = -2)