Adaptive Fisherized KST
// 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 KST', 'AF_KST', false)

// -- Input --
string resolution = input.timeframe(defval='', title='Resolution', group='KST', inline='1')
int min = input.int(defval=3, minval=2, title='Min Length', tooltip='Used for adaptive mode', group='KST', inline='2')
int length = input.int(defval=100, minval=2, title='MA Length', group='KST', inline='2')
int roc1Length = input.int(defval=5, minval=2, title='ROC Length 1', group='KST', inline='4')
int roc2Length = input.int(defval=10, minval=2, title='ROC Length 2', group='KST', inline='4')
int roc3Length = input.int(defval=15, minval=2, title='ROC Length 3', group='KST', inline='5')
int roc4Length = input.int(defval=30, minval=2, title='ROC Length 4', group='KST', inline='5')
bool isFisherized = input.bool(defval=true, title='Enable Inverse Fisher Transform', group='KST', inline='9')
bool isFibWeightingEnabled = input.bool(title='Enable Fibonacci Weighting', defval=true, tooltip='Applied to the 4 ROCs used in KST calculation')
string adaptiveMode = input.string(defval='Median', title='Choose Adaptive Mode', options=['None', 'Hilbert Transform', 'Inphase-Quadrature Transform', 'Median', 'Homodyne Discriminator'], tooltip='Just applied on KST MA', group='KST', inline='10')
bool isHighlighted = input.bool(defval=true, title='Enable Positive/Negative Highlightning', group='KST', inline='11')
string signalMode = input.string(defval='Zero Line', title='Choose Signal Mode', options=['Cross', 'Cross Filtered', 'Zero Line', 'None'], tooltip='Cross Filtered uses above/below zero line as filter', group='KST', inline='12')

string kstMaType = input.string(defval='T3', title='Choose KST MA', options=['RMA', 'VWMA', 'KAMA', 'T3', 'JMA'], group='MA', inline='1')
float t3Hot = input.float(title='T3 – Smoothing Factor (hot)', defval=0.7, step=0.1, minval=0.00001, group='MA', inline='2')
string t3Type = input.string(title='T3 – Type', defval='T3 New', options=['T3 Normal', 'T3 New'], group='MA', inline='2')
int jmaPhase = input.int(title='JMA – Phase', defval=50, minval=-100, maxval=100, tooltip='The phase is used to shift the input data before applying the smoothing algorithm. The phase shift can help to identify the turning points of the data.', group='MA', inline='3')
int jmaPower = input.int(title='JMA – Power', defval=2, minval=2, tooltip='The power is used to adjust the sensitivity of the JMA to changes in the input data. Higher values of the power will make the JMA more sensitive to changes in the data.', group='MA', inline='3')

bool isNetEnabled = input.bool(false, 'Enable NET', group='Smoothing', inline='1')
int netLength = input.int(3, 'NET Length', minval=2, group='Smoothing', inline='1')
bool isHannWindowEnabled = input.bool(false, 'Enable Hann Window', group='Smoothing', inline='2')
int hannWindowLength = input.int(3, 'Hann Window Length', minval=2, group='Smoothing', inline='2')
bool isHodrickPrescrottFilterEnabled = input.bool(true, 'Enable Hodrick Prescott Filter', group='Smoothing', inline='3')
float hpLambda = input.float(defval=5000, title='Smoothing Factor (Lambda)', minval=1, group='Smoothing', inline='4')
int hpLength = input.int(defval=300, title='Filter Length', minval=1, group='Smoothing', inline='4')

string divergenceMode = input.string('None', 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 superGreen = color.rgb(34, 255, 207)
color superPurple = color.rgb(152, 105, 255)
color lightPurple = color.rgb(193, 133, 243)
color lightGray = color.rgb(214, 214, 214)
color quickSilver = color.rgb(163, 163, 163)

// Divergence colors
color bullColor = lightPurple
color bearColor = superPurple
color hiddenBullColor = bullColor
color hiddenBearColor = bearColor
color noneColor = lightGray
color textColor = color.white

// -- Functions --
[src, _volume] = request.security(syminfo.tickerid, resolution, [close[1], volume[1]], barmerge.gaps_off, barmerge.lookahead_on)

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 kstLength) => 2 / (kstLength + 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])
    
    IntPeriod := 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])
    
    DomCycle := math.ceil(CycPart * SmoothPeriod) < 1 ? 1 : math.ceil(CycPart * SmoothPeriod)
    
    // Limit dominant cycle
    DomCycle := DomCycle < min ? min : DomCycle > max ? max : DomCycle
    math.round(DomCycle)

// Noise Elimination Technology (NET) – Credits to @blackcat1402
doNet(float _series, simple 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

// Credits to @hajixde
hodrickPrescottFilter(float src, float lambda, int length) =>
    // Construct Arrays
    a = array.new_float(length, 0.0)
    b = array.new_float(length, 0.0)
    c = array.new_float(length, 0.0)
    d = array.new_float(length, 0.0)
    e = array.new_float(length, 0.0)
    f = array.new_float(length, 0.0)

    l1 = length - 1
    l2 = length - 2

    for i = 0 to l1 by 1
        array.set(a, i, lambda * -4)
        array.set(b, i, src[i])
        array.set(c, i, lambda * -4)
        array.set(d, i, lambda * 6 + 1)
        array.set(e, i, lambda)
        array.set(f, i, lambda)

    array.set(d, 0, lambda + 1.0)
    array.set(d, l1, lambda + 1.0)
    array.set(d, 1, lambda * 5.0 + 1.0)
    array.set(d, l2, lambda * 5.0 + 1.0)

    array.set(c, 0, lambda * -2.0)
    array.set(c, l2, lambda * -2.0)

    array.set(a, 0, lambda * -2.0)
    array.set(a, l2, lambda * -2.0)

    // Solve the optimization issue
    float r = array.get(a, 0)
    float s = array.get(a, 1)
    float t = array.get(e, 0)
    float xmult = 0.0

    for i = 1 to l2 by 1
        xmult := r / array.get(d, i - 1)
        array.set(d, i, array.get(d, i) - xmult * array.get(c, i - 1))
        array.set(c, i, array.get(c, i) - xmult * array.get(f, i - 1))
        array.set(b, i, array.get(b, i) - xmult * array.get(b, i - 1))

        xmult := t / array.get(d, i - 1)
        r := s - xmult * array.get(c, i - 1)
        array.set(d, i + 1, array.get(d, i + 1) - xmult * array.get(f, i - 1))
        array.set(b, i + 1, array.get(b, i + 1) - xmult * array.get(b, i - 1))

        s := array.get(a, i + 1)
        t := array.get(e, i)
        t

    xmult := r / array.get(d, l2)
    array.set(d, l1, array.get(d, l1) - xmult * array.get(c, l2))

    x = array.new_float(length, 0)
    array.set(x, l1, (array.get(b, l1) - xmult * array.get(b, l2)) / array.get(d, l1))
    array.set(x, l2, (array.get(b, l2) - array.get(c, l2) * array.get(x, l1)) / array.get(d, l2))

    for j = 0 to length - 3 by 1
        i = length - 3 - j
        array.set(x, i, (array.get(b, i) - array.get(f, i) * array.get(x, i + 2) - array.get(c, i) * array.get(x, i + 1)) / array.get(d, i))

    // Construct the output
    HP = array.get(x, 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

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

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

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

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

// Jurik Moving Average (JMA) - Credits to @everget
jma(float _src, int _length, int _phase=50, int _power=2) => 
    phaseRatio = _phase < -100 ? 0.5 : _phase > 100 ? 2.5 : _phase / 100 + 1.5
    
    beta = 0.45 * (_length - 1) / (0.45 * (_length - 1) + 2)
    alpha = math.pow(beta, _power)
    
    jma = 0.0
    
    e0 = 0.0
    e0 := (1 - alpha) * _src + alpha * nz(e0[1])
    
    e1 = 0.0
    e1 := (_src - e0) * (1 - beta) + beta * nz(e1[1])
    
    e2 = 0.0
    e2 := (e0 + phaseRatio * e1 - nz(jma[1])) * math.pow(1 - alpha, 2) + math.pow(alpha, 2) * nz(e2[1])
    
    jma := e2 + nz(jma[1])   

// T3 - Credits to @loxx
t3(float _src, float _length, float hot=1, 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

ma(float source, int length, string type) =>
    float ma = switch type
        'SMA' => sma(source, length)
        'KAMA' => kama(source, length)
        'VWMA' => vwma(source, length)
        'RMA' => rma(source, length)
        'T3' => t3(source, length, t3Hot, t3Type)
        'JMA' => jma(source, length, jmaPhase, jmaPower)
        => source

roc(float _src, float _length) => 
    100 * (_src - _src[_length]) / _src[_length]

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

if (adaptiveMode != 'None')
    if (kstLength < min)
        kstLength := min

    roc1Length := kstLength
    roc2Length := math.round(kstLength * 1.5)
    roc3Length := kstLength * 2
    roc4Length := kstLength * 3

if (isHodrickPrescrottFilterEnabled)
    src := hodrickPrescottFilter(src, hpLambda, hpLength)    
    _volume := hodrickPrescottFilter(_volume, hpLambda, length)

if (isHannWindowEnabled)
    src := doHannWindow(src, hannWindowLength)
    _volume := doHannWindow(src, hannWindowLength)

float roc1 = roc(src, roc1Length)
float roc2 = roc(src, roc2Length)
float roc3 = roc(src, roc3Length)
float roc4 = roc(src, roc4Length)

float kst = isFibWeightingEnabled ? (0.236) * roc1 + (0.382 * roc2) + (0.50 * roc3) + (0.618) * roc4 : roc1 + (2 * roc2) + (3 * roc3) + (4 * roc4)    
float kstMa = ma(kst, kstLength, kstMaType)

if (isFisherized)
    kst := fisherize(kst)
    kstMa := fisherize(kstMa)
else
    kst := normalize(kst * 1000, -1, 1)
    kstMa := ma(kst, kstLength, kstMaType)

if (isNetEnabled)
    kst := doNet(kstMa, netLength)

bool rocLong = kst > 0
bool rocShort = kst < 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))
kstGradient = color.from_gradient(kst, bottom_value=-0.5, top_value=0.5, top_color=superGreen, bottom_color=superPurple)
kstMaPlot = plot(kstMa, title='KST MA', color=kstGradient, linewidth=2)
kstPlot = plot(kst, title='KST', color=kstGradient, linewidth=2)
fill(kstPlot, kstMaPlot, color=kstGradient)

fill(zeroLine, topBand, color=(isHighlighted and rocLong) ? color.new(color.rgb(34, 255, 207), 65) : na)
fill(zeroLine, bottomBand, color=(isHighlighted and rocShort) ? color.new(color.rgb(152, 105, 255), 65) : na)

// Signals
var string openTrade = ''
var string signalCache = ''

bool enterLong = false
bool enterShort = false
bool exitLong = false
bool exitShort = false

if (signalMode == 'Cross')
    enterLong := ta.crossover(kst, kstMa)
    enterShort := ta.crossunder(kst, kstMa)

if (signalMode == 'Cross Filtered')
    enterLong := ta.crossover(kst, kstMa) and rocLong
    enterShort := ta.crossunder(kst, kstMa) and rocShort
    exitLong := ta.crossunder(kst, kstMa)
    exitShort := ta.crossover(kst, kstMa)

if (signalMode == 'Zero Line')
    enterLong := ta.crossover(kst, 0)
    enterShort := ta.crossunder(kst, 0)

// Signal Cache
// It shouldn't happen that multiple signals are executed in one instance
// The cache tries to order the signals correctly
if (signalMode == 'Cross Filtered')
    if (signalCache == 'long entry')
        signalCache := ''
        enterLong := true
    else if (signalCache == 'short entry')
        signalCache := ''
        enterShort := 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 and exitLong)
            enterShort := false
            signalCache := 'short entry'
        if (exitLong)
            openTrade := ''
    else if (openTrade == 'short')
        if (exitLong)
            exitLong := false
        if (enterShort)
            enterShort := false
        if (enterLong)
            enterShort := true

        if (enterLong and exitShort)
            enterLong := false
            signalCache := 'long entry'
        if (exitShort)
            openTrade := ''

plotshape(enterLong ? -1.2 : na, title='BUY', style=shape.triangleup, color=superGreen, size=size.tiny, location=location.absolute)
plotshape(enterShort ? 1.2 : na, title='SELL', style=shape.triangledown, color=superPurple, size=size.tiny, location=location.absolute)
plotshape(exitLong ? -1.2 : na, title='BE', style=shape.circle, color=superGreen, size=size.tiny, location=location.absolute)
plotshape(exitShort ? 1.2 : na, title='SE', style=shape.circle, color=superPurple, size=size.tiny, location=location.absolute)

alertcondition(enterLong, title='ENTER LONG', message='KST – ENTER LONG')
alertcondition(exitLong, title='EXIT LONG', message='KST – EXIT LONG')
alertcondition(enterShort, title='ENTER SHORT', message='KST – ENTER SHORT')
alertcondition(exitShort, title='EXIT SHORT', message='KST – EXIT SHORT')

// -- Divergences – Credits to @tista --
[_high, _low] = request.security(syminfo.tickerid, resolution, [high[1], low[1]], barmerge.gaps_off, barmerge.lookahead_on)

float top = na
float bot = na
top := ta.pivothigh(kst, lbL, lbR)
bot := ta.pivotlow(kst, lbL, lbR)

bool phFound = na(top) ? false : true
bool plFound = na(bot) ? false : true

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

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

bool bullCond = priceLL and kstHL and plFound

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

plotshape(
	 (((divergenceMode == 'Regular') or (divergenceMode == 'Both')) and bullCond) ? kst[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 KST found', message='KST – Regular Bullish Divergence')

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

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

bool hiddenBullCond = priceHL and kstLL and plFound

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

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

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

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

bool bearCond = priceHH and kstLH and phFound

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

plotshape(
	 (((divergenceMode == 'Regular') or (divergenceMode == 'Both')) and bearCond)  ? kst[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 KST found', message='KST – Regular Bullish Divergence')

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

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

bool hiddenBearCond = priceLH and kstHH and phFound

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

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

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