Adaptive Fisherized Trend Intensity Index 
// 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 Trend Intensity Index', 'AF_TII', precision=2)

// -- Input --
float source = input.source(title='Source', defval=close, group='TII', inline='1')
string resolution = input.timeframe(defval='', title='Choose Resolution', group='TII', inline='1')
string maType = input.string(defval='KAMA', title='Choose MA', options=['SMA', 'SMMA', 'EMA', 'ZLEMA', 'DEMA', 'TEMA', 'HMA', 'EHMA', 'RMA', 'WMA', 'VWMA', 'KAMA'], group='TII', inline='2')
int maLength = input.int(title='MA Length', defval=200, group='TII', inline='3', tooltip='When adaptive mode is enabled, this value is used as the maximum length.')
int min = input.int(title='Min MA Length', defval=50, group='TII', inline='4', tooltip='When adaptive mode is enabled, this value is used as the minimum length.')
float upperLevel = input.float(title='Upper Level', defval=0.8, group='TII', inline='5')
float lowerLevel = input.float(title='Lower Level', defval=-0.8, group='TII', inline='5')
string adaptiveMode = input.string('None', 'Choose Adaptive Mode', options=['Hilbert Transform', 'Inphase-Quadrature Transform', 'Median', 'Homodyne Discriminator', 'None'], group='TII', inline='6')
bool isHighlighted = input.bool(defval=true, title='Enable Trend Highlightning', group='TII', inline='7')
bool isFisherized = input.bool(defval=false, title='Enable Fisherization', group='TII', inline='8')
bool isHannEnabled = input.bool(true, 'Enable Hann Window Smoothing', group='TII', inline='9')
int hannLength = input.int(2, 'Hann Window Length', minval=2, tooltip='Smoothing Length', group='TII', inline='9')

// -- 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 lightOrange = color.rgb(241, 205, 167)
color peachPuff = color.rgb(244, 215, 184)

color transpLightGray = color.new(lightGray, 50)
color transpMagicMint = color.new(magicMint, 45)
color transpRajah = color.new(rajah, 45)
color sidetrendColor = color.new(peachPuff, 45)

// -- Global States --
[_src, _close, _high, _low, _volume] = request.security(syminfo.tickerid, resolution, [source[1], close[1], high[1], low[1], volume[1]], barmerge.gaps_off, barmerge.lookahead_on)

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

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

// Simple Moving Average (SMA)
sma(float _src, int _period) =>
    sum = 0.0
    for i = 0 to _period - 1
        sum := sum + _src[i] / _period
    sum

// Standard Deviation
isZero(val, eps) => math.abs(val) <= eps

sum(fst, snd) =>
    EPS = 1e-10
    res = fst + snd
    if isZero(res, EPS)
        res := 0
    else
        if not isZero(res, 1e-4)
            res := res
        else
            15

stdev(src, length) =>
    avg = sma(src, length)
    sumOfSquareDeviations = 0.0
    for i = 0 to length - 1
        sum = sum(src[i], - avg)
        sumOfSquareDeviations := sumOfSquareDeviations + sum * sum

    stdev = math.sqrt(sumOfSquareDeviations / length)

// Measure of difference between the series and it's ma
dev(float source, int length, float ma) =>
    mean = sma(source, length)
    sum = 0.0
    for i = 0 to length - 1
        val = source[i]
        sum := sum + math.abs(val - mean)
    dev = sum/length

// Exponential Moving Average (EMA)
ema(float _src, int _period) =>
    alpha = 2 / (_period + 1)
    sum = 0.0
    sum := na(sum[1]) ? sma(_src, _period) : alpha * _src + (1 - alpha) * nz(sum[1])

// Zero-lag Exponential Moving Average (ZLEMA)
zlema(float _src, int _period) =>
    lag = math.round((_period - 1) / 2)
    ema_data = _src + (_src - _src[lag])
    zl= ema(ema_data, _period)

// Smoothed Moving Average (SMMA)
smma(float _src, int _period) =>
    tmpSMA = sma(_src, _period)
    tmpVal = 0.0
    tmpVal := na(tmpVal[1]) ? tmpSMA : (tmpVal[1] * (_period - 1) + _src) / _period

// Double Expotential Moving Average (DEMA)
dema(float _src, int _period) =>
    (2 * ema(_src, _period) - ema(ema(_src, _period), _period))

// Exponential Hull Moving Average (EHMA)
ehma(float _src, int _period) =>
    tmpVal = math.max(2, _period)
    ema(2 * ema(_src, tmpVal / 2) - ema(_src, tmpVal), math.round(math.sqrt(tmpVal)))

// Kaufman's Adaptive Moving Average (KAMA)
kama(float _src, int _period) =>
    if (bar_index > _period)
        tmpVal = 0.0
        tmpVal := nz(tmpVal[1]) +  math.pow(((math.sum(math.abs(_src - _src[1]), _period) != 0) ? (math.abs(_src - _src[_period]) / math.sum(math.abs(_src - _src[1]), _period)) : 0) * (0.666 - 0.0645) + 0.0645, 2) * (_src - nz(tmpVal[1]))
        tmpVal

// Chandelier Momentum Oscillator (CMO)
f1(m) => m >= 0.0 ? m : 0.0
f2(m) => m >= 0.0 ? 0.0 : -m
percent(nom, div) => 100 * nom / div

cmo(_src, _cmoLength) =>
    if (bar_index > _cmoLength)
        float mommOut = ta.change(_src)
        float m1 = f1(mommOut)
        float m2 = f2(mommOut)
        float sm1 = math.sum(m1, _cmoLength)
        float sm2 = math.sum(m2, _cmoLength)
        float cmo = percent(sm1 - sm2, sm1 + sm2)

// Variable Index Dynamic Average (VIDYA)
vidya(float _src, int _period) =>
    if (bar_index > _period)
        float cmo = math.abs(cmo(_src, _period))
        float alpha = 2 / (_period + 1)
        float vidya = 0.0
        vidya := _src * alpha * cmo + nz(vidya[1]) * (1 - alpha * cmo)  

// Tripple Exponential Moving Average (TEMA)
tema(float _src, int _period) =>
    ema1 = ema(_src, _period)
    ema2 = ema(ema1, _period)
    ema3 = ema(ema2, _period)
    (3 * ema1) - (3 * ema2) + ema3

// Rolling Moving Average (RMA)
rma(float _src, int _period) =>
    rmaAlpha = 1 / _period
	sum = 0.0
	sum := na(sum[1]) ? sma(_src, _period) : rmaAlpha * _src + (1 - rmaAlpha) * nz(sum[1])

// Weighted Moving Average (WMA)
wma(float _src, int _period) =>
    norm = 0.0
    sum = 0.0
    for i = 0 to _period - 1
        weight = (_period - i) * _period
        norm := norm + weight
        sum := sum + _src[i] * weight
    sum / norm

// Hull Moving Average (HMA)
hma(float _src, int _period) =>
    tmpVal = math.max(2, _period)
    wma((2 * wma(_src, tmpVal / 2)) - wma(_src, tmpVal), math.round(math.sqrt(tmpVal)))

// Volume Weighted Moving Averga (VWMA)
vwma(float _src, int _period) =>
    sma(_src * _volume, _period) / sma(_volume, _period)

ma(float source, int length, string type) =>
    float ma = switch type
        'SMA' => sma(source, length)
        'EMA' => ema(source, length)
        'ZLEMA' => zlema(source, length)
        'EHMA' => ehma(source, length)
        'DEMA' => dema(source, length)
        'KAMA' => kama(source, length)
        'SMMA' => smma(source, length)
        'RMA' => rma(source, length)
        'WMA' => wma(source, length)
        'VWMA' => vwma(source, length)
        'HMA' => hma(source, length)
        'TEMA' => tema(source, length)

// -- Calculation --
// Trend Intensity Index Calculation
int _maLength = maLength

if (isHannEnabled)
    _src := doHannWindow(_src, hannLength)

if (adaptiveMode == 'Inphase-Quadrature Transform')
    _maLength := math.round(getIqDc(_close, min, maLength) * 4)
if (adaptiveMode == 'Homodyne Discriminator')
    _maLength := math.round(getHdDc(_close, min, maLength) * 2)
if (adaptiveMode == 'Median')
    _maLength := math.round(getMedianDc(_close, min, maLength) * 2)
if (adaptiveMode == 'Hilbert Transform')
    _maLength := math.round(getHtDc(_close) * 2)

if (_maLength < min)
    _maLength := min

ma = ma(_src, _maLength, maType)

positiveSum = 0.0
negativeSum = 0.0

for i = 0 to min - 1 by 1
    price = nz(_src[i])
    avg = nz(ma[i])
    positiveSum := positiveSum + (price > avg ? price - avg : 0)
    negativeSum := negativeSum + (price > avg ? 0 : avg - price)
    negativeSum

float tii = 100 * positiveSum / (positiveSum + negativeSum)

if (isFisherized)
    tii := fisherize(0.1 * (tii - 50))
else
    tii := normalize(tii, -1, 1)

tiiColor = tii > upperLevel ? transpMagicMint : tii < lowerLevel ? transpRajah : (tii < upperLevel and tii > lowerLevel) ? sidetrendColor : na
plot(tii, title='TII', linewidth=2, color=tiiColor)

topBand = plot(1, 'Top Band', color=transpLightGray)
upperBand = plot(upperLevel, 'Upper Band', color=transpLightGray)
zeroLine = plot(0, 'Zero Line', color=transpLightGray)
lowerBand = plot(lowerLevel, 'Lower Band', color=transpLightGray)
bottomBand = plot(-1, 'Bottom Band', color=transpLightGray)
plot = plot(tii, title='TII', color=languidLavender)

fill(upperBand, topBand, color=tii > upperLevel ? tiiColor : na)
fill(lowerBand, bottomBand, color=tii < lowerLevel ? tiiColor : na)
fill(lowerBand, upperBand, color=(tii < upperLevel and tii > lowerLevel) ? tiiColor : na)
