Adaptive Fisherized ROC
// 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 Fisherized ROC', 'AF_ROC', false)

// -- Input --
string rocResolution = input.timeframe(defval='', title='Resolution', group='ROC', inline='1')
int min = input.int(defval=4, minval=2, title='Min Length', group='ROC', inline='2')
int rocLength = input.int(defval=13, minval=2, title='Length', group='ROC', inline='2')
bool isRocFisherized = input.bool(defval=true, title='Enable Fisherization', group='ROC', inline='3')
string adaptiveMode = input.string('Inphase-Quadrature Transform', 'Choose Adaptive Mode', options=['Hilbert Transform', 'Inphase-Quadrature Transform', 'Median', 'Homodyne Discriminator', 'None'], group='ROC', inline='4')
bool isHighlighted = input.bool(defval=true, title='Enable Positive/Negative Highlightning', group='ROC', inline='5')

string sourceSmoothingMode = input.string('Hann Window', 'Choose Smoothing Mode', options=['T3', 'T3 New', 'Hann Window', 'NET', 'Super Smoother', 'None'], group='Source Smoothing', inline='1')
int sourceSmoothingLength = input.int(3, 'Source Smoothing Length', minval=2, group='Source Smoothing', inline='2')
bool isAdaptiveLengthAppliedOnSourceSmoothing = input.bool(false, 'Override source smoothing length with the adaptive length', group='Source Smoothing', inline='3')

string indicatorSmoothingMode = input.string('T3', 'Choose Smoothing Mode', options=['T3', 'T3 New', 'Hann Window', 'NET', 'Super Smoother', 'None'], group='Indicator Smoothing', inline='1')
int indicatorSmoothingLength = input.int(5, 'Indicator Smoothing Length', minval=2, group='Indicator Smoothing', inline='2')
bool isAdaptiveLengthAppliedOnIndicatorSmoothing = input.bool(true, 'Override indicator smoothing length with the adaptive length', group='Indicator Smoothing', inline='3')

string divergenceMode = input.string('Both', title='Divergence Mode', options=['Regular', 'Hidden', 'Both', 'None'], group='Divergences', inline='1')
bool isDivergencePlotEnabled = input.bool(false, title='Enable Divergence Lines Plot', group='Divergences', inline='2')
int rangeUpper = input.int(title='Max Lookback Range', minval=1, defval=60, group='Divergences', inline='3')
int rangeLower = input.int(title='Min Lookback Range', minval=1, defval=5, group='Divergences', inline='3')
int lbR = input.int(title='Pivot Lookback Right', minval=2, defval=12, group='Divergences', inline='4')
int lbL = input.int(title='Pivot Lookback Left', minval=2, defval=12, group='Divergences', inline='4')

// -- 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)

// Divergence colors
color bullColor = mediumAquamarine
color bearColor = lightPurple
color hiddenBullColor = bullColor
color hiddenBearColor = bearColor
color noneColor = color.new(color.white, 100)
color textColor = color.white

// -- Functions --
inRange(bool cond) =>
    bars = ta.barssince(cond == true)
    rangeLower <= bars and bars <= rangeUpper

// Apply normalization
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)

// Inverse Fisher Transformation (IFT)
fisherize(float _value) => (math.exp(2 * _value) - 1) / (math.exp(2 * _value) + 1)

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

// 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

    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 : math.round(Period * CycPart)
    IntPeriod

// 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
    math.round(length)

// Get dominant cyber cycle – Hilbert Transform -- Credits to @DasanC
getHtDc(float src) =>
    Imult = .635
    Qmult = .338
    PI = 3.14159
    InPhase = 0.0
    Quadrature = 0.0
    Phase = 0.0
    DeltaPhase = 0.0
    InstPeriod = 0.0
    Period = 0.0
    Value4 = 0.0

    if bar_index > 5
        // Detrend src
        Value3 = src - src[7]
        // Compute InPhase and Quadrature components
        InPhase := 1.25 * (Value3[4] - Imult * Value3[2]) + Imult * nz(InPhase[3])
        Quadrature := Value3[2] - Qmult * Value3 + Qmult * nz(Quadrature[2])
        // Use ArcTangent to compute the current phase
        if math.abs(InPhase + InPhase[1]) > 0
            Phase := 180 / PI * math.atan(math.abs((Quadrature + Quadrature[1]) / (InPhase + InPhase[1])))
            Phase
        // Resolve the ArcTangent ambiguity
        if InPhase < 0 and Quadrature > 0
            Phase := 180 - Phase
            Phase
        if InPhase < 0 and Quadrature < 0
            Phase := 180 + Phase
            Phase
        if InPhase > 0 and Quadrature < 0
            Phase := 360 - Phase
            Phase
        // Compute a differential phase, resolve phase wraparound, and limit delta phase errors
        DeltaPhase := Phase[1] - Phase
        if Phase[1] < 90 and Phase > 270
            DeltaPhase := 360 + Phase[1] - Phase
            DeltaPhase
        if DeltaPhase < 1
            DeltaPhase := 1
            DeltaPhase
        if DeltaPhase > 60
            DeltaPhase := 60
            DeltaPhase
        // Sum DeltaPhases to reach 360 degrees. The sum is the instantaneous period.
        for i = 0 to 50 by 1
            Value4 += DeltaPhase[i]
            if Value4 > 360 and InstPeriod == 0
                InstPeriod := i
                InstPeriod
        // Resolve Instantaneous Period errors and smooth
        if InstPeriod == 0
            nz(InstPeriod[1])
        Period := .25 * InstPeriod + .75 * Period[1]
        math.round(Period)

// 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

// Noise Elimination Technology (NET) – Credits to @blackcat1402
doNet(float _series, int _netLength, int _lowerBand=-1, int _upperBand=1) =>
    var netX = array.new_float(102)
    var netY = array.new_float(102)
    num = 0.00 
    denom = 0.00 
    net = 0.00
    trigger = 0.00

    for count = 1 to _netLength 
        array.set(netX, count, nz(_series[count - 1]))
        array.set(netY, count, -count)

    num := 0

    for count = 2 to _netLength 
        for k = 1 to count - 1 
            num := num - math.sign(nz(array.get(netX, count)) - nz(array.get(netX, k)))
    denom := 0.5 * _netLength * (_netLength - 1)
    net := num / denom
    trigger := 0.05 + 0.9 * nz(net[1]) 
    trigger := normalize(trigger, _lowerBand, _upperBand)

// 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 src, int length, string smoothingMode, float t3Hot=0.5) => 
    switch smoothingMode
        'T3' => t3(src, length, t3Hot, 'T3')
        'T3 New' => t3(src, length, t3Hot, 'T3 New')
        'Super Smoother' => ss(src, length)
        'Hann Window' => doHannWindow(src, length)
        'NET' => doNet(src, length)
        => src

// -- ROC Calculation --
[src, _high, _low] = request.security(syminfo.tickerid, rocResolution, [close[1], high[1], low[1]], barmerge.gaps_off, barmerge.lookahead_on)

// Source Smoothing
if (not isAdaptiveLengthAppliedOnSourceSmoothing)
    src := doSmoothing(src, sourceSmoothingLength, sourceSmoothingMode)

int _length = switch adaptiveMode
    'Hilbert Transform' => math.round(getHtDc(src) / 2)
    'Inphase-Quadrature Transform' => math.round(getIqDc(src, min, rocLength) / 2)
    'Median' => getMedianDc(src, getAlpha(rocLength)),
    'Homodyne Discriminator' => math.round(getHdDc(src, min, rocLength))
    => rocLength

if (_length < min and adaptiveMode != 'None')
    _length := min

if (isAdaptiveLengthAppliedOnSourceSmoothing)
    src := doSmoothing(src, _length, sourceSmoothingMode)

float roc = 100 * (src - src[_length]) / src[_length]

// Indicator Smoothing
if (isAdaptiveLengthAppliedOnIndicatorSmoothing)
    roc := doSmoothing(roc, _length, indicatorSmoothingMode)
else
    roc := doSmoothing(roc, indicatorSmoothingLength, indicatorSmoothingMode)

if (isRocFisherized)
    roc := fisherize(roc)

bool rocLong = roc >= 0
bool rocShort = roc < 0

topBand = plot(1, 'Top Band', color=color.new(lightGray, 50))
zeroLine = plot(0, 'Zero Line', color=color.new(lightGray, 50))
bottomBand = plot(-1, 'Bottom Band', color=color.new(lightGray, 50))
rocPlot = plot(roc, title='ROC', color=languidLavender)

fill(zeroLine, topBand, color=(isHighlighted and rocLong) ? color.new(magicMint, 90) : na)
fill(zeroLine, bottomBand, color=(isHighlighted and rocShort) ? color.new(rajah, 90) : na)

// -- Divergences – Credits to @tista --
float top = na
float bot = na
top := ta.pivothigh(roc, lbL, lbR)
bot := ta.pivotlow(roc, lbL, lbR)
bool phFound = na(top) ? false : true
bool plFound = na(bot) ? false : true

// -- Regular Bullish --
// Osc: Higher Low
bool rocHL = roc[lbR] > ta.valuewhen(plFound, roc[lbR], 1) and inRange(plFound[1])

// Price: Lower Low
bool priceLL = _low[lbR] < ta.valuewhen(plFound, _low[lbR], 1)

bool bullCond = priceLL and rocHL and plFound

plot(
	 (((divergenceMode == 'Regular') or (divergenceMode == 'Both')) and plFound and isDivergencePlotEnabled) ? roc[lbR] : na,
	 offset=-lbR,
	 title='Regular Bullish',
	 linewidth=1,
	 color=(bullCond ? bullColor : noneColor),
	 editable = false)

plotshape(
	 (((divergenceMode == 'Regular') or (divergenceMode == 'Both')) and bullCond) ? roc[lbR] : na,
	 offset=-lbR,
	 title='Regular Bullish Label',
	 text='R',
	 style=shape.labelup,
	 location=location.absolute,
	 color=bullColor,
	 textcolor=textColor,
	 editable = false)

alertcondition(bullCond, title='Regular bullish divergence in roc found', message='roc – Regular Bullish Divergence')

// -- Hidden Bullish Divergence --
// Osc: Lower Low
bool rocLL = roc[lbR] < ta.valuewhen(plFound, roc[lbR], 1) and inRange(plFound[1])

// Price: Higher Low
bool priceHL = _low[lbR] > ta.valuewhen(plFound, _low[lbR], 1)

bool hiddenBullCond = priceHL and rocLL and plFound

plot(
	 (((divergenceMode == 'Hidden') or (divergenceMode == 'Both')) and plFound and isDivergencePlotEnabled) ? roc[lbR] : na,
	 offset=-lbR,
	 title='Hidden Bullish',
	 linewidth=1,
	 color=(hiddenBullCond ? hiddenBullColor : noneColor), 
	 editable = false)
	 
plotshape(
	 (((divergenceMode == 'Hidden') or (divergenceMode == 'Both')) and hiddenBullCond) ? roc[lbR] : na,
	 offset=-lbR,
	 title='Hidden Bullish Label',
	 text='H',
	 style=shape.labelup,
	 location=location.absolute,
	 color=hiddenBullColor,
	 textcolor=textColor,
	 editable = false)

alertcondition(hiddenBullCond, title='Hidden bullish divergence in roc found', message='roc – Hidden Bullish Divergence')

// -- Regular Bullish Divergence --
// Osc: Lower High
bool rocLH = roc[lbR] < ta.valuewhen(phFound, roc[lbR], 1) and inRange(phFound[1])

// Price: Higher High
bool priceHH = _high[lbR] > ta.valuewhen(phFound, _high[lbR], 1)

bool bearCond = priceHH and rocLH and phFound

plot(
	 (((divergenceMode == 'Regular') or (divergenceMode == 'Both')) and phFound and isDivergencePlotEnabled) ? roc[lbR] : na,
	 offset=-lbR,
	 title='Regular Bearish',
	 linewidth=1,
	 color=(bearCond ? bearColor : noneColor), 
	 editable = false)

plotshape(
	 (((divergenceMode == 'Regular') or (divergenceMode == 'Both')) and bearCond)  ? roc[lbR] : na,
	 offset=-lbR,
	 title='Regular Bearish Label',
	 text='R',
	 style=shape.labeldown,
	 location=location.absolute,
	 color=bearColor,
	 textcolor=textColor,
	 editable = false)

alertcondition(bearCond, title='Regular bearish divergence in roc found', message='roc – Regular Bullish Divergence')

// -- Hidden Bearish Divergence --
// Osc: Higher High
bool rocHH = roc[lbR] > ta.valuewhen(phFound, roc[lbR], 1) and inRange(phFound[1])

// Price: Lower High
bool priceLH = _high[lbR] < ta.valuewhen(phFound, _high[lbR], 1)

bool hiddenBearCond = priceLH and rocHH and phFound

plot(
	 (((divergenceMode == 'Hidden') or (divergenceMode == 'Both')) and phFound and isDivergencePlotEnabled) ? roc[lbR] : na,
	 offset=-lbR,
	 title='Hidden Bearish',
	 linewidth=1,
	 color=(hiddenBearCond ? hiddenBearColor : noneColor), 
	 editable=false)

plotshape(
	 (((divergenceMode == 'Hidden') or (divergenceMode == 'Both')) and hiddenBearCond) ? roc[lbR] : na,
	 offset=-lbR,
	 title='Hidden Bearish Label',
	 text='H',
	 style=shape.labeldown,
	 location=location.absolute,
	 color=hiddenBearColor,
	 textcolor=textColor, 
	 editable=false)

alertcondition(hiddenBearCond, title='Hidden bearish divergence in roc found', message='roc – Hidden Bearish Divergence')

// Try to fix scale with hidden plots
plot(1.35, 'Scale fix', transp=100, editable=false, color=color.black)
plot(-1.35, 'Scale fix', transp=100, editable=false, color=color.black)