Adaptive Fisherized Stochastic Center of Gravity
// 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 Stochastic Center of Gravity', 'AF_SCG', overlay=false)

// -- Input --
// Gradient color schemes
var string SH1 = 'Red ⮀ Green'
var string SH2 = 'Purple ⮀ Blue'
var string SH3 = 'Yellow ⮀ Dark Green'
var string SH4 = 'White ⮀ Black'
var string SH5 = 'White ⮀ Blue'
var string SH6 = 'White ⮀ Red'
var string SH7 = 'Rainbow'

string stochResolution = input.timeframe(defval='', title='Resolution', group='Stochastic/CG', inline='1')
float source = input.source(close, 'Source', group='Stochastic/CG', inline='2')
int min = input.int(5, 'Min. Period', minval=1, group='Stochastic/CG', inline='3', tooltip='Used as min length for adaptive mode')
int length = input.int(20, 'Period', minval=1, group='Stochastic/CG', inline='4', tooltip='Used as length or max length when adaptive mode is enabled')
bool isStoch = input.bool(false, 'Use Stochastic Instead Of CG', tooltip='For overbought/oversold and cross Signals', group='Stochastic/CG', inline='5')
bool isFisherized = input.bool(true, 'Enable Inverse Fisher Transform (IFT)', group='Stochastic/CG', inline='6')
string signalMode = input.string('Fast Overbought/Oversold', 'Choose Signal Mode', options=['Fast Overbought/Oversold', 'Overbought/Oversold', 'Cross', 'Stoch/CG Cross', 'None'], tooltip='Upper and lower lines are the overbought/oversold trigger lines', group='Stochastic/CG', inline='7')

var bool isGradientEnabled = input.bool(true, 'Enable Gradient', group='Gradient', inline='1')
var bool isGradientInverted = input.bool(true, 'Invert Colors', group='Gradient', inline='1')
var string gradientScheme = input.string(SH2, 'Choose Color Scheme',  options = [SH1, SH2, SH3, SH4, SH5, SH6, SH7], group='Gradient', inline='2')

string adaptiveMode = input.string('Hilbert Transform', 'Choose Adaptive Mode', options=['Hilbert Transform', 'Inphase-Quadrature Transform', 'Median', 'Homodyne Discriminator', 'None'], group='Adaptiveness', inline='1')
bool isLengthPlotEnabled = input.bool(false, 'Enable Length Plot', tooltip='Interesting to see the adaptive length', group='Adaptiveness', inline='2')

string smoothingMode = input.timeframe('T3', 'Choose Smoothing Mode', options=['T3', 'Hann Window', 'None'], group='Smoothing', inline='1', tooltip='Smooths the price')
int smoothingLength = input.int(3, 'Smoothing Length', minval=2, group='Smoothing', inline='2')
float t3Hot = input.float(title='T3 Smoothing Factor', defval=0.5, minval=0.00001, maxval=1, step=0.1, group='Smoothing', inline='3', tooltip='Called hot in code')
string t3Type = input.string(title='T3 Type', defval='T3 Normal', options=['T3 Normal', 'T3 New'], group='Smoothing', inline='3')

bool isNetEnabled = input.bool(false, 'Enable NET', group='Smoothing', inline='3', tooltip='Ehlers Noise Elmimination Technique')
int netLength = input.int(9, 'NET Length', minval=2, group='Smoothing', inline='4')

// -- Global States --
[_high, _low, src] = request.security(syminfo.tickerid, stochResolution, [high[1], low[1], source[1]], barmerge.gaps_off, barmerge.lookahead_on)
bool areBuySellLabelsEnabled = signalMode != 'None'

// -- 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 transpLightGray = color.new(lightGray, 50)

// -- Functions --
// Get highest val in period
getHighest(src, len) =>
    H = src[len]
    for i = 0 to len by 1
        if src[i] > H
            H := src[i]
            H
    H

// Get lowest val in period
getLowest(src, len) =>
    L = src[len]
    for i = 0 to len by 1
        if src[i] < L
            L := src[i]
            L
    L

// Get normal stochastic
stoch(src, len) => 100 * (src - getLowest(_low, len)) / (getHighest(_high, len) - getLowest(_low, len))

// 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])
    
    IntPeriod := math.round(Period * CycPart) < 1 ? 1 : math.round(Period * CycPart)
    IntPeriod

// Get dominant cyber cycle – Inphase-Quadrature -- Credits to @DasanC
getIQ(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 > max ? max : lenIQ
    math.round(length)

// Get dominant cyber cycle – Hilbert Transform -- Credits to @DasanC
getHT(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

var color[] colors = array.new_color(na)

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

// Credits to @LucF, https://www.pinecoders.com/faq_and_code/#how-can-i-rescale-an-indicator-from-one-scale-to-another
// Rescales series with known min/max to the color array size.
rescale(_src, _min, _max) =>
    var int _size = array.size(colors) - 1
    int _colorStep = int(_size * (_src - _min)) / int(math.max(_max - _min, 10e-10))
    _colorStep := _colorStep > _size ? _size : _colorStep < 0 ? 0 : _colorStep
    int(_colorStep)

colGrad(_src, _min, _max) => array.get(colors, rescale(_src, _min, _max))

// -- Calculation --
// Price Smoothing
if (smoothingMode == 'Hann Window')
    src := doHannWindow(src, smoothingLength)

if (smoothingMode == 'T3')
    src := t3(src, smoothingLength, t3Hot, t3Type)

// Adaptiveness
float alpha = getAlpha(length)
float cyclePart = 0.5

// Cannot override input length variable
int _length = length

switch adaptiveMode
    'Hilbert Transform' => _length := getHT(src)
    'Inphase-Quadrature Transform' => _length := getIQ(src, min, _length)
    'Median' => _length := getMedianDc(src, alpha, cyclePart),
    'Homodyne Discriminator' => _length := getHdDc(src, min, _length)

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

plot(isLengthPlotEnabled ? getHT(src) / 2 : na, 'HT', color=skyBlue)
plot(isLengthPlotEnabled ? getIQ(src, min, _length) / 2 : na, 'IQ', color=maximumBluePurple)
plot(isLengthPlotEnabled ? getMedianDc(src, alpha, cyclePart) / 2 : na, 'M', color=languidLavender)
plot(isLengthPlotEnabled ? getHdDc(src, min, _length) : na, 'HD', color=lightPurple)

plot(isLengthPlotEnabled ? _length : na, 'CG/Stoch Length', color=maximumYellowRed)

// CoG/CG
Num = 0.0
Denom = 0.0
CG = 0.0
MaxCG = 0.0
MinCG = 0.0
Value1 = 0.0
Value2 = 0.0
Value3 = 0.0
for i = 0 to _length - 1 by 1
    Num += (1 + i) * src[i]
    Denom += src[i]

if (Denom != 0)
    CG := -Num / Denom + (_length + 1) / 2

MaxCG := getHighest(CG, _length)
MinCG := getLowest(CG, _length)
if (MaxCG != MinCG)
    Value1 := (CG - MinCG) / (MaxCG - MinCG)

Value2 := (4 * Value1 + 3 * Value1[1] + 2 * Value1[2] + Value1[3]) / 10
Value3 := .5 * math.log((1 + 1.98 * (Value2 - .5)) / (1 - 1.98 * (Value2 - .5)))

// Stoch
int topBand = 1
int bottomBand = -1
float upperBand = 0.9
float lowerBand = -0.9
int zeroLine = 0

float _stoch = stoch(src, _length)

if (isStoch and (not isFisherized) and (signalMode != 'Stoch/CG Cross'))
    topBand := 100
    bottomBand := 0
    upperBand := 80
    lowerBand := 20
    zeroLine := 50

if ((not isStoch) and (not isFisherized))
    // topBand := math.round(getHighest(Value3, 20))
    // bottomBand := math.round(getLowest(Value3, 20))
    topBand := 3
    bottomBand := -3
    lowerBand := -2.5
    upperBand := 2.5
    zeroLine := 0

if (isNetEnabled or isFisherized)
    topBand := 1
    bottomBand := -1
    upperBand := 0.9
    lowerBand := -0.9
    zeroLine := 0

// Normalization and Fisherization
if (isFisherized or (signalMode == 'Stoch/CG Cross'))
    Value3 := fisherize(Value3)
    _stoch := fisherize(0.1 * (_stoch - 50))
    // _stoch := fisherize(normalize(_stoch, lowerBand, upperBand))

// Indicator Smoothing
if (isNetEnabled)
    Value3 := doNet(Value3, smoothingLength)
    _stoch := doNet(_stoch, smoothingLength)

// Gradient - Credits @e2e4mfck
// Fill the array with colors only on the first iteration of the script
if barstate.isfirst
    if gradientScheme == SH1
        array.push(colors, #ff0000), array.push(colors, #ff2c00), array.push(colors, #fe4200), array.push(colors, #fc5300), array.push(colors, #f96200), array.push(colors, #f57000), array.push(colors, #f07e00), array.push(colors, #ea8a00), array.push(colors, #e39600), array.push(colors, #dca100), array.push(colors, #d3ac00), array.push(colors, #c9b600), array.push(colors, #bfc000), array.push(colors, #b3ca00), array.push(colors, #a6d400), array.push(colors, #97dd00), array.push(colors, #86e600), array.push(colors, #71ee00), array.push(colors, #54f700), array.push(colors, #1eff00)
    else if gradientScheme == SH2
        array.push(colors, #ff00d4), array.push(colors, #f71fda), array.push(colors, #ef2ee0), array.push(colors, #e63ae5), array.push(colors, #de43ea), array.push(colors, #d44bee), array.push(colors, #cb52f2), array.push(colors, #c158f6), array.push(colors, #b75df9), array.push(colors, #ac63fb), array.push(colors, #a267fe), array.push(colors, #966bff), array.push(colors, #8b6fff), array.push(colors, #7e73ff), array.push(colors, #7276ff), array.push(colors, #6479ff), array.push(colors, #557bff), array.push(colors, #437eff), array.push(colors, #2e80ff), array.push(colors, #0082ff)
    else if gradientScheme == SH3
        array.push(colors, #fafa6e), array.push(colors, #e0f470), array.push(colors, #c7ed73), array.push(colors, #aee678), array.push(colors, #97dd7d), array.push(colors, #81d581), array.push(colors, #6bcc86), array.push(colors, #56c28a), array.push(colors, #42b98d), array.push(colors, #2eaf8f), array.push(colors, #18a48f), array.push(colors, #009a8f), array.push(colors, #00908d), array.push(colors, #008589), array.push(colors, #007b84), array.push(colors, #0c707d), array.push(colors, #196676), array.push(colors, #215c6d), array.push(colors, #275263), array.push(colors, #2a4858)
    else if gradientScheme == SH4
        array.push(colors, #ffffff), array.push(colors, #f0f0f0), array.push(colors, #e1e1e1), array.push(colors, #d2d2d2), array.push(colors, #c3c3c3), array.push(colors, #b5b5b5), array.push(colors, #a7a7a7), array.push(colors, #999999), array.push(colors, #8b8b8b), array.push(colors, #7e7e7e), array.push(colors, #707070), array.push(colors, #636363), array.push(colors, #575757), array.push(colors, #4a4a4a), array.push(colors, #3e3e3e), array.push(colors, #333333), array.push(colors, #272727), array.push(colors, #1d1d1d), array.push(colors, #121212), array.push(colors, #000000)
    else if gradientScheme == SH5
        array.push(colors, #ffffff), array.push(colors, #f4f5fa), array.push(colors, #e9ebf5), array.push(colors, #dee1f0), array.push(colors, #d3d7eb), array.push(colors, #c8cde6), array.push(colors, #bdc3e1), array.push(colors, #b2b9dd), array.push(colors, #a7b0d8), array.push(colors, #9ca6d3), array.push(colors, #919dce), array.push(colors, #8594c9), array.push(colors, #7a8bc4), array.push(colors, #6e82bf), array.push(colors, #6279ba), array.push(colors, #5570b5), array.push(colors, #4768b0), array.push(colors, #385fab), array.push(colors, #2557a6), array.push(colors, #004fa1)
    else if gradientScheme == SH6
        array.push(colors, #ffffff), array.push(colors, #fff4f1), array.push(colors, #ffe9e3), array.push(colors, #ffded6), array.push(colors, #ffd3c8), array.push(colors, #fec8bb), array.push(colors, #fdbdae), array.push(colors, #fbb2a1), array.push(colors, #f8a794), array.push(colors, #f69c87), array.push(colors, #f3917b), array.push(colors, #f0856f), array.push(colors, #ec7a62), array.push(colors, #e86e56), array.push(colors, #e4634a), array.push(colors, #df563f), array.push(colors, #db4933), array.push(colors, #d63a27), array.push(colors, #d0291b), array.push(colors, #cb0e0e)
    else if gradientScheme == SH7
        array.push(colors, #E50000), array.push(colors, #E6023B), array.push(colors, #E70579), array.push(colors, #E908B7), array.push(colors, #E00BEA), array.push(colors, #A70DEB), array.push(colors, #6E10ED), array.push(colors, #3613EE), array.push(colors, #162DEF), array.push(colors, #1969F1), array.push(colors, #1CA4F2), array.push(colors, #1FDFF4), array.push(colors, #22F5D2), array.push(colors, #25F69C), array.push(colors, #28F867), array.push(colors, #2CF933), array.push(colors, #5DFA2F), array.push(colors, #96FC32), array.push(colors, #CDFD35), array.push(colors, #FFF938)
    
    // Invert colors in array
    if isGradientInverted
        array.reverse(colors)

// Colorize the bounded source
color stochGradient = isGradientEnabled ? colGrad(_stoch, bottomBand, topBand) : skyBlue
color stochTriggerGradient = isGradientEnabled ? colGrad(_stoch[1], bottomBand, topBand) : color.new(skyBlue, 50)
color cgGradient = isGradientEnabled ? colGrad(Value3, bottomBand, topBand) : maximumBluePurple
color cgTriggerGradient = isGradientEnabled ? colGrad(Value3[1], bottomBand, topBand) : color.new(maximumBluePurple, 50)

bool isCgPlotEnabled = (not isStoch) or (signalMode == 'Stoch/CG Cross')
bool isStochPlotEnabled = isStoch or (signalMode == 'Stoch/CG Cross')

stochPlot = plot(isStochPlotEnabled ? _stoch : na, 'Stoch', stochGradient, editable=false)
stochTriggerPlot = plot((isStochPlotEnabled and (signalMode != 'Stoch/CG Cross')) ? _stoch[1] :na, 'Stoch Trigger', stochTriggerGradient, editable=false)
cgPlot = plot(isCgPlotEnabled ? Value3 : na, 'CG', cgGradient, editable=false)
cgTriggerPlot = plot((isCgPlotEnabled and (signalMode != 'Stoch/CG Cross')) ? Value3[1] : na, 'CG Trigger', cgTriggerGradient, editable=false)
plot(zeroLine, 'Zero Line', color=transpLightGray)
plot(upperBand, 'Upper Band', color=transpLightGray)
plot(lowerBand, 'Lower Band', color=transpLightGray)
plot(topBand, 'Top Band', color=transpLightGray)
plot(bottomBand, 'Bottom Band', color=transpLightGray)

fill(stochPlot, stochTriggerPlot, color=isGradientEnabled ? stochTriggerGradient : color.new(skyBlue, 50), editable=false)
fill(cgPlot, cgTriggerPlot, color=isGradientEnabled ? cgTriggerGradient : color.new(languidLavender, 50), editable=false)

// -- Signals --
bool longSignal = false
bool shortSignal = false

if (areBuySellLabelsEnabled)
    float baseLine = isStoch ? _stoch : Value3

    // Check slope
    int lookback = 2
    float slope = (baseLine[lookback] - baseLine) / (ta.lowest(baseLine, lookback) - ta.highest(baseLine, lookback))
    slope := (not slope) ? 0 : slope

    if (signalMode == 'Overbought/Oversold')
        longSignal := ta.crossover(baseLine, lowerBand)
        shortSignal := ta.crossunder(baseLine, upperBand)
    if (signalMode == 'Fast Overbought/Oversold')
        // longSignal := (math.round(slope[1]) == 0) and (ta.crossover(baseLine, lowerBand) or ta.crossover(baseLine, lowerBand)[1] and math.round(slope) > 0)
        // shortSignal := (math.round(slope[1]) == 0) and (ta.crossunder(baseLine, upperBand) or ta.crossover(baseLine, upperBand)[1] and math.round(slope) < 0)
        longSignal := (math.round(baseLine[2], 3) == math.round(baseLine[1], 3)) and baseLine > baseLine[1] and baseLine < lowerBand
        shortSignal := (math.round(baseLine[2], 3) == math.round(baseLine[1], 3)) and baseLine < baseLine[1] and baseLine > upperBand
    if (signalMode == 'Stoch/CG Cross')   
        longSignal := ta.cross(Value3, _stoch) and _stoch < zeroLine and Value3 < zeroLine
        shortSignal := ta.cross(Value3, _stoch) and _stoch > zeroLine and Value3 > zeroLine
    if ((signalMode == 'Cross') and (not isStoch))
        longSignal := ta.crossover(Value3, Value3[1]) and Value3 < zeroLine
        shortSignal := ta.crossunder(Value3, Value3[1]) and Value3 > zeroLine
    if ((signalMode == 'Cross') and isStoch)
        longSignal := ta.crossover(_stoch[1], _stoch) and _stoch < zeroLine
        shortSignal := ta.crossunder(_stoch[1], _stoch) and _stoch > zeroLine

plotshape(longSignal ? bottomBand - 0.5 : na, title='BUY', style=shape.triangleup, color=magicMint, size=size.tiny, location=location.absolute)
plotshape(shortSignal ? topBand + 0.5: na, title='SELL', style=shape.triangledown, color=rajah, size=size.tiny, location=location.absolute)

// -- Alerts --
alertcondition(longSignal, title='BUY', message='Adaptive Fisherized Center of Gravity -> BUY')
alertcondition(shortSignal, title='SELL', message='Adaptive Fisherized Center of Gravity -> SELL')