Adaptive Fusion ADX Vortex
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © simwai

//@version=5
indicator('Adaptive Fusion ADX Vortex', 'A_FAV')

// -- Colors --
color maximumYellowRed = color.rgb(255, 203, 98) // yellow
color rajah = color.rgb(242, 166, 84) // orange
color magicMint = color.rgb(171, 237, 198)
color lightPurple = color.rgb(193, 133, 243)
color languidLavender = color.rgb(232, 215, 255)
color maximumBluePurple = color.rgb(181, 161, 226)
color skyBlue = color.rgb(144, 226, 244)
color lightGray = color.rgb(214, 214, 214)
color quickSilver = color.rgb(163, 163, 163)
color mediumAquamarine = color.rgb(104, 223, 153)
color carrotOrange = color.rgb(239, 146, 46)
color lightOrange = color.rgb(241, 205, 167)
color peachPuff = color.rgb(244, 215, 184)

// -- Inputs --
indicatorMode = input.string('Fusion', 'Choose Indicator Mode', ['ADX DI', 'Vortex', 'Fusion'], group='General', inline='1')
resolution = input.timeframe('', 'Resolution', group='General', inline='2')
adaptiveMode = input.string('None', 'Choose Adaptive Mode', options=['Inphase-Quadrature Transform', 'Median', 'Homodyne Discriminator', 'VHF', 'None'], tooltip='Used for CCI and MA length calculation', group='General', inline='3')
minLength = input.int(2, minval=2, title='Min Length', group='General', inline='3')
smoothingMode = input.string('Hann Window', 'Choose Smoothing Mode', options=['T3', 'T3 New', 'Hann Window', 'Super Smoother', 'None'], group='General', inline='4')
_smoothing = input.int(9, minval=2, title='Smoothing Length', group = 'General', inline='4')
isSpecialRuleEnabled = input.bool(false, 'Enable Special Enter Filter Rule', group='General', inline='5', tooltip='Either + or - plot needs to be unter ADX/Vortex/Fusion')

exitsGroup = 'Exits'
useCross = input.bool(title='Use DI/Vortex/Fusion Cross', defval=true, group=exitsGroup, inline='2')
useThreshold = input.bool(title='Use Threshold', defval=true, group=exitsGroup, inline='2')

_length = input.int(34, minval=2, maxval=499, title='Length', group = 'ADX DI/Vortex', inline='1', tooltip='Acts as max length when adaptive mode is on')
_threshold = input.float(0.2, minval=0, maxval=1.0, step=0.1, title='Threshold', group = 'ADX DI/Vortex', inline='2')
isThresholdColored = input.bool(false, 'Enable Threshold Coloring', group='ADX DI/Vortex', inline='3', tooltip='Applies a lighter color to threshold when ADX DI/Vortex/Fusion value is above it')
isThresholdDynamic = input.bool(false, 'Enable Dynamic Threshold', group='ADX DI/Vortex', inline='4')
dynamicThresholdLookback = input.int(34, 'Dynamic Threshold Lookback', group='ADX DI/Vortex', inline='4', tooltip='EXPERIMENTAL')

adxWeight = input.float(0.3, 'ADX Weight', group='Fusion', inline='1')
vortexWeight = input.float(0.7, 'Vortex Weight', group='Fusion', inline='1')

isHurstExponentFilterEnabled = input.bool(false, 'Enable Hurst Exponent Filter', group='Hurst Exponent Filter', inline='1')
isHurstExponentFilterSmoothed = input.bool(false, 'Enable Smoothing', group='Hurst Exponent Filter', inline='1')
len = input.int(300, 'Sample Size', minval=40, group='Hurst Exponent Filter', tooltip='lookback period for HE, Optimal Size > 50, Larger Size More Accurate and More lag', inline='2')
Bsc = input.int(16, 'Base Scale', options=[4, 8, 16], inline='3', group='Hurst Exponent Filter')
Msc = input.int(2, 'Max Scale', options=[2, 4], inline='3', group='Hurst Exponent Filter', tooltip='Select larger Value(Max and Base scale) when Sample Size is larger')

// -- Global States --
isVortexEnabled = indicatorMode == 'Vortex'
isFusionEnabled = indicatorMode == 'Fusion'
isAdxEnabled = indicatorMode == 'ADX DI'

[_close, _low, _high] = request.security(symbol = syminfo.tickerid, timeframe = resolution, expression = [close[1], low[1], high[1]], gaps =  barmerge.gaps_off, lookahead = barmerge.lookahead_on) 

// -- Functions --
// Get dominant cyber cycle – Median – Credits to @blackcat1402
getMedianDc(float Price, float alpha=0.07, simple float CycPart=0.5) =>
    Smooth = 0.00
    Cycle = 0.00
    Q1 = 0.00
    I1 = 0.00
    DeltaPhase = 0.00
    MedianDelta = 0.00
    DC = 0.00
    InstPeriod = 0.00
    Period = 0.00
    I2 = 0.00
    Q2 = 0.00
    IntPeriod = 0.0

    Smooth := (Price + 2 * nz(Price[1]) + 2 * nz(Price[2]) + nz(Price[3])) / 6
    Cycle := (1 - .5 * alpha) * (1 - .5 * alpha) * (Smooth - 2 * nz(Smooth[1]) + nz(Smooth[2])) + 2 * (1 - alpha) * nz(Cycle[1]) - (1 - alpha) * (1 - alpha) * nz(Cycle[2])
    Cycle := bar_index < 7 ? (Price - 2 * nz(Price[1]) + nz(Price[2])) / 4 : Cycle

    Q1 := (.0962 * Cycle + .5769 * nz(Cycle[2]) - .5769 * nz(Cycle[4]) - .0962 * nz(Cycle[6])) * (.5 + .08 * nz(InstPeriod[1]))
    I1 := nz(Cycle[3])
    DeltaPhase := Q1 != 0 and nz(Q1[1]) != 0 ? (I1 / Q1 - nz(I1[1]) / nz(Q1[1])) / (1 + I1 * nz(I1[1]) / (Q1 * nz(Q1[1]))) : DeltaPhase
    DeltaPhase := DeltaPhase < 0.1 ? 0.1 : DeltaPhase
    DeltaPhase := DeltaPhase > 1.1 ? 1.1 : DeltaPhase

    MedianDelta := ta.median(DeltaPhase, 5)
    DC := MedianDelta == 0.00 ? 15.00 : 6.28318 / MedianDelta + .5
    InstPeriod := .33 * DC + .67 * nz(InstPeriod[1])
    Period := .15 * InstPeriod + .85 * nz(Period[1])
    //it can add filter to Period here
    //IntPeriod := round((4*Period + 3*nz(Period[1]) + 2*nz(Period[3]) + nz(Period[4])) / 20)
    IntPeriod := math.round(Period * CycPart) > 34 ? 34 : math.round(Period * CycPart) < 1 ? 1 : Period * CycPart

// Get dominant cyber cycle – Inphase-Quadrature -- Credits to @DasanC
getIqDc(float src, int min, int max) =>
    PI = 3.14159265359
    P = src - src[7]
    lenIQ = 0.0
    lenC = 0.0
    imult = 0.635
    qmult = 0.338
    inphase = 0.0
    quadrature = 0.0
    re = 0.0
    im = 0.0
    deltaIQ = 0.0
    instIQ = 0.0
    V = 0.0

    inphase := 1.25 * (P[4] - imult * P[2]) + imult * nz(inphase[3])
    quadrature := P[2] - qmult * P + qmult * nz(quadrature[2])
    re := 0.2 * (inphase * inphase[1] + quadrature * quadrature[1]) + 0.8 * nz(re[1])
    im := 0.2 * (inphase * quadrature[1] - inphase[1] * quadrature) + 0.8 * nz(im[1])
    if re != 0.0
        deltaIQ := math.atan(im / re)
        deltaIQ
    for i = 0 to max by 1
        V += deltaIQ[i]
        if V > 2 * PI and instIQ == 0.0
            instIQ := i
            instIQ
    if instIQ == 0.0
        instIQ := nz(instIQ[1])
        instIQ
    lenIQ := 0.25 * instIQ + 0.75 * nz(lenIQ[1], 1)
    length = lenIQ < min ? min : lenIQ

// Get Dominant Cyber Cycle - Homodyne Discriminator With Hilbert Dominant Cycle – Credits to @blackcat1402
getHdDc(float Price, int min, int max, simple float CycPart = 0.5) =>
    Smooth = 0.00
    Detrender = 0.00
    I1 = 0.00
    Q1 = 0.00
    jI = 0.00
    jQ = 0.00
    I2 = 0.00
    Q2 = 0.00
    Re = 0.00
    Im = 0.00
    Period = 0.00
    SmoothPeriod = 0.00
    pi = 2 * math.asin(1)
    DomCycle = 0.0

    //Hilbert Transform
    Smooth := bar_index > 7 ? (4 * Price + 3 * nz(Price[1]) + 2 * nz(Price[2]) + nz(Price[3])) / 10 : Smooth
    Detrender := bar_index > 7 ? (.0962 * Smooth + .5769 * nz(Smooth[2]) - .5769 * nz(Smooth[4]) - .0962 * nz(Smooth[6])) * (.075 * nz(Period[1]) + .54) : Detrender
    //Compute InPhase and Quadrature components
    Q1 := bar_index > 7 ? (.0962 * Detrender + .5769 * nz(Detrender[2]) - .5769 * nz(Detrender[4]) - .0962 * nz(Detrender[6])) * (.075 * nz(Period[1]) + .54) : Q1
    I1 := bar_index > 7 ? nz(Detrender[3]) : I1

    //Advance the phase of I1 and Q1 by 90 degrees
    jI := (.0962 * I1 + .5769 * nz(I1[2]) - .5769 * nz(I1[4]) - .0962 * nz(I1[6])) * (.075 * nz(Period[1]) + .54)
    jQ := (.0962 * Q1 + .5769 * nz(Q1[2]) - .5769 * nz(Q1[4]) - .0962 * nz(Q1[6])) * (.075 * nz(Period[1]) + .54)

    //Phasor addition for 3 bar averaging
    I2 := I1 - jQ
    Q2 := Q1 + jI

    //Smooth the I and Q components before applying the discriminator
    I2 := .2 * I2 + .8 * nz(I2[1])
    Q2 := .2 * Q2 + .8 * nz(Q2[1])

    //Homodyne Discriminator
    Re := I2 * nz(I2[1]) + Q2 * nz(Q2[1])
    Im := I2 * nz(Q2[1]) - Q2 * nz(I2[1])
    Re := .2 * Re + .8 * nz(Re[1])
    Im := .2 * Im + .8 * nz(Im[1])

    Period := Im != 0 and Re != 0 ? 2 * pi / math.atan(Im / Re) : Period
    Period := Period > 1.5 * nz(Period[1]) ? 1.5 * nz(Period[1]) : Period
    Period := Period < .67 * nz(Period[1]) ? .67 * nz(Period[1]) : Period

    Period := Period < min ? min : Period
    Period := Period > max ? max : Period
    Period := .2 * Period + .8 * nz(Period[1])
    SmoothPeriod := .33 * Period + .67 * nz(SmoothPeriod[1])
    //it can add filter to Period here
    DomCycle := math.ceil(CycPart * SmoothPeriod) > 34 ? 34 : math.ceil(CycPart * SmoothPeriod) < 1 ? 1 : math.ceil(CycPart * SmoothPeriod)
    // Limit dominant cycle
    DomCycle := DomCycle < min ? min : DomCycle
    DomCycle := DomCycle > max ? max : DomCycle

// Hann Window Smoothing – Credits to @cheatcountry
doHannWindow(float _series, float _hannWindowLength) =>
    sum = 0.0, coef = 0.0
    for i = 1 to _hannWindowLength
        cosine = 1 - math.cos(2 * math.pi * i / (_hannWindowLength + 1))
        sum := sum + (cosine * nz(_series[i - 1]))
        coef := coef + cosine
    h = coef != 0 ? sum / coef : 0

// T3 - Credits to @loxx
t3(float _src, float _length, float hot=0.5, string clean='T3')=>
    a = hot
    _c1 = -a * a * a
    _c2 = 3 * a * a + 3 * a * a * a
    _c3 = -6 * a * a - 3 * a - 3 * a * a * a
    _c4 = 1 + 3 * a + a * a * a + 3 * a * a
    
    alpha = 0.
    
    if (clean == 'T3 New')
        alpha := 2.0 / (2.0 + (_length - 1.0) / 2.0)
    else 
        alpha := 2.0 / (1.0 + _length)

    _t30 = _src, _t31 = _src
    _t32 = _src, _t33 = _src
    _t34 = _src, _t35 = _src
    
    _t30 := nz(_t30[1]) + alpha * (_src -  nz(_t30[1]))
    _t31 := nz(_t31[1]) + alpha * (_t30 - nz(_t31[1]))
    _t32 := nz(_t32[1]) + alpha * (_t31 - nz(_t32[1]))
    _t33 := nz(_t33[1]) + alpha * (_t32 - nz(_t33[1]))
    _t34 := nz(_t34[1]) + alpha * (_t33 - nz(_t34[1]))
    _t35 := nz(_t35[1]) + alpha * (_t34 - nz(_t35[1]))
    out =
         _c1 * _t35 + _c2 * _t34 +
         _c3 * _t33 +  _c4 * _t32
    out

// Super Smoother Function - Credits to @balipour
ss(Series, Period) =>  // Super Smoother Function
    var PI = 2.0 * math.asin(1.0)
    var SQRT2 = math.sqrt(2.0)
    lambda = PI * SQRT2 / Period
    a1 = math.exp(-lambda)
    coeff2 = 2.0 * a1 * math.cos(lambda)
    coeff3 = -math.pow(a1, 2.0)
    coeff1 = 1.0 - coeff2 - coeff3
    filt1 = 0.0
    filt1 := coeff1 * (Series + nz(Series[1])) * 0.5 + coeff2 * nz(filt1[1]) + coeff3 * nz(filt1[2])
    filt1

doSmoothing(float _input, float length) =>
    t3Hot = 0.5

    switch smoothingMode
        'Hann Window' => doHannWindow(_input, length)
        'T3' => t3(_input, length, t3Hot, 'T3')
        'T3 New' => t3(_input, length, t3Hot, 'T3 New')
        'Super Smoother' => ss(_input, length)
        => _input

// VHF - Credits to @loxx
getVhf(float _close, int length) =>
    if (bar_index > length)
        vhfNoise = 0.
        vhfDiff = (_close > nz(_close[1])) ? _close - nz(_close[1]) : nz(_close[1]) - _close
        vhfNoise := nz(vhfNoise[1]) - nz(vhfDiff[length]) + vhfDiff

        HCP = ta.highest(_close, length) 
        LCP = ta.lowest(_close, length) 
        vhf = (HCP - LCP) / vhfNoise  
        int peradapt = math.round(length * vhf * 2.0) 

sma(source, length) => 
    if bar_index > length
        sum = 0.0
        for i = 0 to length - 1
            sum := sum + source[i]
        sum / length

tr() => 
    math.max(math.max(_high - _low, math.abs(_high - _close[1])), math.abs(_low - _close[1]))

rma(src, length) => 
    if bar_index > length
        alpha = 1/length
        sum = 0.0
        sum := na(sum[1]) ? sma(src, length) : alpha * src + (1 - alpha) * nz(sum[1])

dirmov(len, smoothing) =>
	up = ta.change(_high)
	down = -ta.change(_low)
	truerange = rma(tr(), len)
	plus = fixnan(100 * rma(up > down and up > 0 ? up : 0, len) / truerange)
	minus = fixnan(100 * rma(down > up and down > 0 ? down : 0, len) / truerange)
    
    plus := doSmoothing(plus, smoothing)
    minus:= doSmoothing(minus, smoothing)

	[plus, minus]

adx(length, smoothing) => 
	[plus, minus] = dirmov(length, smoothing)
	sum = plus + minus
    part = math.abs(plus - minus) / (sum == 0 ? 1 : sum)
	adx = 100 * t3(part, smoothing)
	[adx, plus, minus]

atr(atrLength) => 
    float tr = tr()
    doSmoothing(tr, atrLength)

vortex(int len, smoothing) =>
    tr = atr(smoothing)
    part = math.max(_high - _high[1], 0)
    part2 = math.max(_low[1] - _low, 0)
    pdm = isFusionEnabled ? part : doSmoothing(part, smoothing)
    mdm = isFusionEnabled ? part2 : doSmoothing(part2, smoothing)
    plus = pdm / tr
    minus = mdm / tr
    absdiff = math.abs(plus - minus)
    vortex = absdiff / (plus + minus)
    [vortex, plus, minus]

length_to_alpha(length) =>
    2 / (length + 1)

ema(source, length) =>
    alpha = length_to_alpha(length)
    ema = 0.0
    ema := alpha * source + (1 - alpha) * nz(ema[1])

normalize(float _src, int _min, int _max) =>
    var float _historicMin = 1.0
    var float _historicMax = -1.0
    _historicMin := math.min(nz(_src, _historicMin), _historicMin)
    _historicMax := math.max(nz(_src, _historicMax), _historicMax)
    _min + (_max - _min) * (_src - _historicMin) / math.max(_historicMax - _historicMin, 1)

// Convert length to alpha
getAlpha(float _length) => 2 / (_length + 1)

getDynamicThreshold(threshold, _length, fusion, _close) =>
    // Calculate the dynamic threshold
    dynamicThreshold = switch adaptiveMode
        'Inphase-Quadrature Transform' => getIqDc(_close, 2, _length)
        'Median' => getMedianDc(_close, getAlpha(_length))
        'Homodyne Discriminator' => getHdDc(_close, 2, _length)
        'VHF' => getVhf(_close, _length)
        => ta.highest(fusion, dynamicThresholdLookback) * 0.5

    // Normalize the dynamic threshold
    dynamicThreshold := normalize(dynamicThreshold, 0, 1) 

// -- Calculation --
// Adaptive Length
length = switch adaptiveMode
    'Inphase-Quadrature Transform' => math.round(getIqDc(_close, minLength, _length))
    'Median' => math.round(getMedianDc(_close, getAlpha(_length)))
    'Homodyne Discriminator' => math.round(getHdDc(_close, minLength, _length))
    'VHF' => math.round(getVhf(_close, _length))
    => _length

smoothing = switch adaptiveMode
    'Inphase-Quadrature Transform' => length
    'Median' => length
    'Homodyne Discriminator' => length
    'VHF' => length
    => _smoothing

if (adaptiveMode != 'None' and minLength > length)
    length := minLength
    smoothing := minLength

// ADX DI
adx = 0.0
plus = 0.0
minus = 0.0
if (isAdxEnabled or isFusionEnabled)
    [_adx, _plus, _minus] = adx(length, smoothing)
    adx := _adx
    plus := _plus
    minus := _minus

// Vortex
vortex = 0.0
vortexPlus = 0.0
vortexMinus = 0.0
if (isVortexEnabled or isFusionEnabled)
    [_vortex, _vortexPlus, _vortexMinus] = vortex(length, smoothing)
    vortex := _vortex
    vortexPlus := _vortexPlus
    vortexMinus := _vortexMinus

// Fusion Mode
fusionMinus = 0.0
fusionPlus = 0.0
fusion = 0.0

if (isFusionEnabled)
    fusionMinus := doSmoothing((minus * adxWeight + vortexMinus * vortexWeight) / (adxWeight + vortexWeight), length)
    fusionPlus := doSmoothing((plus * adxWeight + vortexPlus * vortexWeight) / (adxWeight + vortexWeight), length)
    fusion := (adx * adxWeight + vortex * vortexWeight) / (adxWeight + vortexWeight)

// Normalize ADX DI, Vortex and Fusion
max = 1
min = 0
if (isFusionEnabled)
    adx := normalize(adx, min, max)
    plus := normalize(plus, min, max)
    minus := normalize(minus, min, max)
    vortex := normalize(vortex, min, max)
    vortexPlus := normalize(vortexPlus, min, max)
    vortexMinus := normalize(vortexMinus, min, max)
    fusion := normalize(fusion, min, max)
    fusionPlus := normalize(fusionPlus, min, max)
    fusionMinus := normalize(fusionMinus, min, max)
if (isAdxEnabled)
    adx := normalize(adx, min, max)
    plus := normalize(plus, min, max)
    minus := normalize(minus, min, max)
if (isVortexEnabled)
    vortex := normalize(vortex, min, max)
    vortexPlus := normalize(vortexPlus, min, max)
    vortexMinus := normalize(vortexMinus, min, max)

// Adaptive Threshold
threshold = isThresholdDynamic ? getDynamicThreshold(_threshold, length, fusion, _close) : _threshold
thresholdPlot = plot(threshold, color=fusion > threshold ? color.new(lightGray, 25) : color.new(lightGray, 75), title='Threshold')

// Plot Fusion Mode
fusionPlot = plot(isFusionEnabled ? fusion : na, color=color.new(maximumBluePurple, 90), style=plot.style_area, title='FUSION')
plot(isFusionEnabled ? fusionPlus : na, color=magicMint, title='+FUSION')
plot(isFusionEnabled ? fusionMinus : na, color=rajah, title='-FUSION')

// Plot ADX and DI
adxPlot = plot(isAdxEnabled ? adx : na, color=color.new(maximumBluePurple, 90), style=plot.style_area, title='ADX')
plot(isAdxEnabled ? plus : na, color=magicMint, title='+DI ADX')
plot(isAdxEnabled ? minus : na, color=rajah, title='-DI ADX')

// Plot Vortex
vortexPlot = plot(isVortexEnabled ? vortex : na, color=color.new(maximumBluePurple, 90), style=plot.style_area, title='VORTEX')
plot(isVortexEnabled ? vortexPlus : na, color=magicMint, title='+VORTEX')
plot(isVortexEnabled ? vortexMinus : na, color=rajah, title='-VORTEX')

fill(thresholdPlot, fusionPlot, color=isThresholdColored and isFusionEnabled and fusion > threshold ? color.new(lightGray, 50) : na)
fill(thresholdPlot, adxPlot, color=isThresholdColored and isAdxEnabled and adx > threshold ? color.new(lightGray, 50) : na)
fill(thresholdPlot, vortexPlot, color=isThresholdColored and isVortexEnabled and vortex > threshold ? color.new(lightGray, 50) : na)

// Hurst Exponent - Credits to @balipour
// Declaration
int Min = Bsc
int Max = Msc
var fluc = array.new_float(10)
var scale = array.new_float(10)
float slope = na

// Log Return
r = math.log(_close / _close[1])

// Mean of Log Return
mean = ta.sma(r, len)

// Cumulative Sum
var csum = array.new_float(len)
sum = 0.0
for i = 0 to len - 1 by 1
    sum := r[i] - mean + sum
    array.set(csum, i, sum)

// Root Mean Sum (FLuctuation) function linear trend to calculate error between linear trend and cumulative sum
RMS(N1, N) =>
    // Slicing the array into different segments
    var seq = array.new_float(N)
    int count = 0
    for i = 0 to N - 1 by 1
        count += 1
        array.set(seq, i, count)
    y = array.slice(csum, N1, N1 + N)
    
    // Linear regression measuing trend (N/(N-1) for sample unbiased adjustedment)
    sdx = array.stdev(seq) * math.sqrt(N / (N - 1))
    sdy = array.stdev(y) * math.sqrt(N / (N - 1))
    cov = array.covariance(seq, y) * (N / (N - 1))
    
    // Linear Regression Root Mean Square Error (Detrend)
    r2 = math.pow(cov / (sdx * sdy), 2)
    rms = math.sqrt(1 - r2) * sdy
    rms

// Average of Root Mean Sum Measured each block (Log Scale)
Arms(bar) =>
    num = math.floor(len / bar)
    sumr = 0.0
    for i = 0 to num - 1 by 1
        sumr += RMS(i * bar, bar)
        sumr
    // Log Scale
    avg = math.log10(sumr / num)
    avg

// Approximating Log Scale Function (Saves Sample Size)
fs(x) => math.round(Min * math.pow(math.pow(len / (Max * Min), 0.1111111111), x))

// Set Ten points of Root Mean Square Average along the Y log axis
array.set(fluc, 0, Arms(fs(0)))
array.set(fluc, 1, Arms(fs(1)))
array.set(fluc, 2, Arms(fs(2)))
array.set(fluc, 3, Arms(fs(3)))
array.set(fluc, 4, Arms(fs(4)))
array.set(fluc, 5, Arms(fs(5)))
array.set(fluc, 6, Arms(fs(6)))
array.set(fluc, 7, Arms(fs(7)))
array.set(fluc, 8, Arms(fs(8)))
array.set(fluc, 9, Arms(fs(9)))

// Set Ten Points of data scale along the X log axis
for i = 0 to 9 by 1
    array.set(scale, i, math.log10(fs(i)))

// Slope Measured From RMS and Scale on Log log plot using linear regression
slope := array.covariance(scale, fluc) / array.variance(scale)

// Signals
enterLong = (isAdxEnabled ? adx > threshold and ta.crossover(plus, minus) and adx > adx[5] : false) or (isVortexEnabled ? vortex > threshold and ta.crossover(vortexPlus, vortexMinus) and vortex > vortex[5] : false) or (isFusionEnabled ? fusion > threshold and ta.crossover(fusionPlus, fusionMinus) and fusion > fusion[5] : false)
enterShort = (isAdxEnabled ? adx > threshold and ta.crossunder(plus, minus) and adx > adx[5] : false) or (isVortexEnabled ? vortex > threshold and ta.crossunder(vortexPlus, vortexMinus) and vortex > vortex[5] : false) or (isFusionEnabled ? fusion > threshold and ta.crossunder(fusionPlus, fusionMinus) and fusion > fusion[5] : false)
exitLong = false
exitShort = false

if (isSpecialRuleEnabled)
    specialRule = (isAdxEnabled ? plus < adx or minus < adx : true) and (isVortexEnabled ? vortexPlus < vortex or vortexMinus < vortex : true) and (isFusionEnabled ? fusionPlus < fusion or fusionMinus < fusion : true)
    enterLong := enterLong and specialRule
    enterShort := enterShort and specialRule

if (useCross)
    exitLong := exitLong or (isAdxEnabled ? ta.crossover(minus, plus) : false) or (isVortexEnabled ? ta.crossover(vortexMinus, vortexPlus) : false) or (isFusionEnabled ? ta.crossover(fusionMinus, fusionPlus) : false)
    exitShort := exitShort or (isAdxEnabled ? ta.crossunder(minus, plus) : false) or (isVortexEnabled ? ta.crossunder(vortexMinus, vortexPlus) : false) or (isFusionEnabled ? ta.crossunder(fusionMinus, fusionPlus) : false)

if (useThreshold)
    exitLong := exitLong or (isAdxEnabled ? ta.cross(adx, threshold) : false) or (isVortexEnabled ? ta.cross(vortex, threshold) : false) or (isFusionEnabled ? ta.cross(fusion, threshold) : false)
    exitShort := exitShort or (isAdxEnabled ? ta.cross(adx, threshold) : false) or (isVortexEnabled ? ta.cross(vortex, threshold) : false) or (isFusionEnabled ? ta.cross(fusion, threshold) : false)

// Apply Hurst Exponent Filter
hurst = 0.0
if (isHurstExponentFilterEnabled)
    // Calculate super smoothed Hurst Exponent 
    if (isHurstExponentFilterSmoothed)
        hurst := doSmoothing(slope, smoothing)
    hurstExponentThreshold = 0.4
    if (hurst < hurstExponentThreshold)
        enterLong := false
        exitLong := false
        enterShort := false
        exitShort := false

// Plot Hurst Exponent
plot(hurst, title='HURST EXPONENT', color=color.new(lightGray, 75))

// -- Signal Cache --
// It shouldn't happen that multiple signals are executed in one instance
// The cache tries to order the signals correctly
var string openTrade = ''
var string signalCache = ''

if (signalCache == 'long entry')
    signalCache := ''
    enterLong := true
else if (signalCache == 'short entry')
    signalCache := ''
    enterShort := true

// -- Signal Cache --
// It shouldn't happen that multiple signals are executed in one instance
// The cache tries to order the signals correctly
if (signalCache == 'long entry')
    signalCache := ''
    enterLong := true
else if (signalCache == 'short entry')
    signalCache := ''
    enterShort := true
else if (signalCache == 'long exit')
    signalCache := ''
    exitLong := true
else if (signalCache == 'short exit')
    signalCache := ''
    exitShort := true

if (openTrade == '')
    if (exitShort and (not enterLong))
        exitShort := false
    if (exitLong and (not enterShort))
        exitLong := false
    if (enterLong and exitShort)
        openTrade := 'long'
        exitShort := false
    else if (enterShort and exitLong)
        openTrade := 'short'
        exitLong := false
    else if (enterLong)
        openTrade := 'long'
    else if (enterShort)
        openTrade := 'short'
else if (openTrade == 'long')
    if (exitShort)
        exitShort := false
    if (enterLong)
        enterLong := false
    if (enterShort)
        enterShort := false
        signalCache := 'short entry'
    if (exitLong)
        openTrade := ''
else if (openTrade == 'short')
    if (exitLong)
        exitLong := false
    if (enterShort)
        enterShort := false
    if (enterLong)
        enterLong := false
        signalCache := 'long entry'
    if (exitShort)
        openTrade := ''

bottomLevel = min
topLevel = max
plotshape(max, color=openTrade == 'short' ? color.new(rajah, 50) : color.new(quickSilver, 75), title='SHORT', location=location.absolute)
plotshape(min, color=openTrade == 'long' ? color.new(magicMint, 50) : color.new(quickSilver, 75), title='LONG', location=location.absolute)
plotshape(enterLong ? bottomLevel : na, title='ENTER LONG', text='B', style=shape.labelup, color=mediumAquamarine, textcolor=color.white, size=size.tiny, location=location.absolute)
plotshape(enterShort ? topLevel : na, title='ENTER SHORT', text='S', style=shape.labeldown, color=carrotOrange, textcolor=color.white, size=size.tiny, location=location.absolute)
plotshape(exitLong ? bottomLevel : na, title='EXIT LONG', text='BE', style=shape.labelup, color=mediumAquamarine, textcolor=color.white, size=size.tiny, location=location.absolute)
plotshape(exitShort ? topLevel : na, title='EXIT SHORT', text='SE', style=shape.labeldown, color=carrotOrange, textcolor=color.white, size=size.tiny, location=location.absolute)

// Try to fix scale with hidden plots
plot(1.6, 'Scale fix', color.rgb(0, 0, 0, 100), editable=false)
plot(-0.6, 'Scale fix', color.rgb(0, 0, 0, 100), editable=false)

// -- Alerts --
alertcondition(enterLong, title='ENTER LONG', message='ENTER LONG')
alertcondition(exitLong, title='EXIT LONG', message='EXIT LONG')
alertcondition(enterShort, title='ENTER SHORT', message='ENTER SHORT')
alertcondition(exitShort, title='EXIT SHORT', message='EXIT SHORT')