Stochastic Chebyshev Smoothed With Zero Lag Smoothing
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © peacefulLizard50262

//@version=5
indicator("Stochastic Chebyshev Smoothed With 0 Lag Smoothing", "CSTOCH")
// Custom cosh function
cosh(float x) =>
    (math.exp(x) + math.exp(-x)) / 2

// Custom acosh function
acosh(float x) =>
    x < 1 ? na : math.log(x + math.sqrt(x * x - 1))

// Custom sinh function
sinh(float x) =>
    (math.exp(x) - math.exp(-x)) / 2

// Custom asinh function
asinh(float x) =>
    math.log(x + math.sqrt(x * x + 1))

// Custom inverse tangent function
atan(float x) =>
    math.pi / 2 - math.atan(1 / x)

// Function to calculate the Gaussian weight for a given 'k' and 'smooth_per'
gaussian_weight(k, smooth_per) =>
    // Calculate the standard deviation (sigma) based on the smoothing period
    sigma = smooth_per / 2.0
    // Calculate the exponent part of the Gaussian function
    exponent = -0.5 * math.pow(k / sigma, 2)
    // Calculate and return the Gaussian weight
    math.exp(exponent)

// Function to calculate the zero-lag moving average with Gaussian weights
zero_lag_gwma(src, smooth_per) =>
    // Declare and initialize the source buffer, sum, sumw, and weight variables
    var float[] src_buffer = array.new_float(0, na)
    var sum = 0.0
    var sumw = 0.0
    var weight = 0.0
    // Declare and initialize the gwma1 and output arrays
    var float[] gwma1 = array.new_float(smooth_per, na)
    var float[] output = array.new_float(smooth_per, na)
    
    // Update the source buffer with the new value
    array.push(src_buffer, src)
    
    // Remove the oldest element when the buffer size reaches smooth_per
    if array.size(src_buffer) > smooth_per
        array.shift(src_buffer)

    // Calculate the current buffer size
    buffer_size = array.size(src_buffer)
    
    // Calculate the first Gaussian weighted moving average (gwma1) with Gaussian weights
    for i = 0 to buffer_size - 1
        // Reset sum and sumw for each iteration
        sum := 0.0
        sumw := 0.0
        for k = 0 to smooth_per - 1
            // Check if the index is within the buffer size
            if i + k < buffer_size
                // Calculate the Gaussian weight for the current 'k'
                weight := gaussian_weight(k, smooth_per)
                // Update the sum of weights
                sumw += weight
                // Update the sum of weighted source values
                sum += weight * array.get(src_buffer, i + k)
        // Calculate and set the gwma1 value for the current index
        if sumw != 0
            array.set(gwma1, i, sum / sumw)
        else        
            array.set(gwma1, i, na)
    
    // Calculate the final zero-lag moving average (output) with Gaussian weights
    for i = 0 to buffer_size - 1
        // Reset sum and sumw for each iteration
        sum := 0.0
        sumw := 0.0
        for k = 0 to smooth_per - 1
            // Check if the index is within the buffer size
            if i - k >= 0
                // Calculate the Gaussian weight for the current 'k'
                weight := gaussian_weight(k, smooth_per)
                // Update the sum of weights
                sumw += weight
                // Update the sum of weighted gwma1 values
                sum += weight * array.get(gwma1, i - k)
        // Calculate and set the output value for the current index
        if sumw != 0
            array.set(output, i, sum / sumw)
        else        
            array.set(output, i, na)
    
    // Return the most recent moving average value
    array.get(output, buffer_size - 1)

// Chebyshev Type I Moving Average
chebyshevI(float src, float len, float ripple) =>
    a = 0.
    b = 0.
    g = 0.
    chebyshev = 0.
    
    a := cosh(1 / len * acosh(1 / (1 - ripple)))
    b := sinh(1 / len * asinh(1 / ripple))
    g := (a - b) / (a + b)
    chebyshev := (1 - g) * src + g * nz(chebyshev[1])
    chebyshev
length      = input.int(14, "Stochastic Length", 1)
k_smoothing = input.int(14, "K Chebyshev Filtering", 5)
k_ripple    = input.float(0.05, "K Chebyshev Ripple", 0.01, 0.99, 0.01)
k_extra     = input.int(1, "K Zero Lag Smoothing", 1)
d_smoothing = input.int(20, "D Chebyshev Filtering", 5)
d_ripple    = input.float(0.01, "D Chebyshev Ripple", 0.01, 0.99, 0.01)

stoch = zero_lag_gwma(ta.stoch(chebyshevI(close, k_smoothing, k_ripple), chebyshevI(high, k_smoothing, k_ripple), chebyshevI(low, k_smoothing, k_ripple), length), k_extra)
lag = chebyshevI(stoch, d_smoothing, d_ripple)
plot(stoch, color = #2962FF)
plot(lag, color = #FF6D00)
h0 = hline(80, "Upper Band", color=#787B86)
hline(50, "Middle Band", color=color.new(#787B86, 50))
h1 = hline(20, "Lower Band", color=#787B86)
fill(h0, h1, color=color.rgb(33, 150, 243, 90), title="Background")