CommonFilters
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © lastguru

//@version=5
// @description Collection of some common Filters and Moving Averages. This collection is not encyclopaedic, but to declutter my other scripts. Suggestions are welcome, though. Many filters here are based on the work of John F. Ehlers
library("CommonFilters", true)

// ----------
// Common Moving Averages
// ----------

// @function Simple Moving Average
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export sma(float src, int len) =>
    out = 0.0
    out := math.sum(src, len)/len

// @function Exponential Moving Average
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export ema(float src, int len) =>
    alpha = 2 / (len + 1)
    sum = 0.0
    sum := alpha * src + (1 - alpha) * nz(sum[1])

// @function Wilder's Smoothing (Running Moving Average)
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export rma(float src, int len) =>
    out = 0.0
    out := ((len - 1) * nz(out[1]) + src) / len

// @function Hull Moving Average
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export hma(float src, int len) =>
    out = 0.0
    out := ta.wma(2 * ta.wma(src, len/2) - ta.wma(src, len), math.round(math.sqrt(len)))

// @function Volume Weighted Moving Average
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export vwma(float src, int len) =>
    val = math.sum(src*volume, len)
    vol = math.sum(volume, len)
    val/vol


// ----------
// Fast FIR prefilters
// ----------

// @function Simple denoiser 
// @param src Series to use
// @returns Filtered series
export hp2(float src) =>
    src - (src - 2 * src[1] + src[2]) / 4

// @function Zero at 2 bar cycle period by John F. Ehlers
// @param src Series to use
// @returns Filtered series
export fir2(float src) =>
    (src + src[1]) / 2

// @function Zero at 3 bar cycle period by John F. Ehlers
// @param src Series to use
// @returns Filtered series
export fir3(float src) =>
    (src + src[1] + src[2]) / 3

// @function Zero at 2 bar and 3 bar cycle periods by John F. Ehlers
// @param src Series to use
// @returns Filtered series
export fir23(float src) =>
    (src + 2 * src[1] + 2 * src[2] + src[3]) / 6

// @function Zero at 2, 3 and 4 bar cycle periods by John F. Ehlers
// @param src Series to use
// @returns Filtered series
export fir234(float src) =>
    (src + 2 * src[1] + 3 * src[2] + 3 * src[3] + 2 * src[4] + src[5]) / 12


// ----------
// High Pass Filters
// ----------

// @function High Pass Filter for cyclic components shorter than langth. Part of Roofing Filter by John F. Ehlers
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export hp(float src, int len) =>
    alpha = (math.cos(math.sqrt(2) * math.pi / len) + math.sin(math.sqrt(2) * math.pi / len) - 1) / math.cos(math.sqrt(2) * math.pi / len)
	highpass = 0.0
	highpass := math.pow(1 - alpha / 2, 2) * (src - 2 * nz(src[1]) + nz(src[2])) + 2 * (1 - alpha) * nz(highpass[1]) - math.pow(1 - alpha, 2) * nz(highpass[2])

// @function 2-pole Super Smoother by John F. Ehlers
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export supers2(float src, int len) =>
    arg = math.sqrt(2) * math.pi / len
    
    a1 = math.exp(-arg)
    b1 = 2 * a1 * math.cos(arg)
    
    c2 = b1
    c3 = -math.pow(a1, 2)
    c1 = 1 - c2 - c3
    
    ssf = 0.0
    ssf := c1 * (src + nz(src[1])) / 2 + c2 * nz(ssf[1]) + c3 * nz(ssf[2])

// @function Filt11 is a variant of 2-pole Super Smoother with error averaging for zero-lag response by John F. Ehlers
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export filt11(float src, int len) =>
    arg = math.sqrt(2) * math.pi / len
    
    a1 = math.exp(-arg)
    b1 = 2 * a1 * math.cos(arg)
    
    c2 = b1
    c3 = -math.pow(a1, 2)
    c1 = 1 - c2 - c3
    
    ssf = 0.0
    ssf := c1 * (src + nz(src[1])) / 2 + c2 * nz(ssf[1]) + c3 * nz(ssf[2])
    
    e1 = 0.0
    e1 := c1 * (src - ssf) + c2 * nz(e1[1]) + c3 * nz(e1[2])
    
    ssf + e1

// @function 3-pole Super Smoother by John F. Ehlers
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export supers3(float src, int len) =>
    arg = math.pi / len
    
    a1 = math.exp(-arg)
    b1 = 2 * a1 * math.cos(math.sqrt(3) * arg)
    c1 = math.pow(a1, 2)
    
    coef2 = b1 + c1
    coef3 = -(c1 + b1 * c1)
    coef4 = math.pow(c1, 2)
    coef1 = 1 - coef2 - coef3 - coef4
    
    ssf = 0.0
    ssf := coef1 * (src + nz(src[1])) / 2 + coef2 * nz(ssf[1]) + coef3 * nz(ssf[2]) + coef4 * nz(ssf[3])


// ----------
// Windowing FIR Filters
// ----------

// @function Hann Window Filter by John F. Ehlers
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export hannFIR(float src, int len) =>
    hann = 0.0
    coef = 0.0
    for count = 0 to len - 1
        m = 1 - math.cos(2 * math.pi * (count+1) / (len+1))
        hann := hann + m * nz(src[count])
        coef := coef + m
    hann := coef != 0 ? hann / coef : 0

// @function Hamming Window Filter (inspired by John F. Ehlers). Simplified implementation as Pedestal input parameter cannot be supplied, so I calculate it from the supplied length
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export hammingFIR(float src, int len) =>
    pedestal = len / 2
    filt = 0.0
    coef = 0.0
    for count = 0 to len - 1
        sine = math.sin((pedestal + (180 - 2 * pedestal) * count / (len - 1)) * math.pi / 180)
        filt := filt + sine * nz(src[count])
        coef := coef + sine
    filt := coef != 0 ? filt / coef : 0

// @function Triangle Window Filter by John F. Ehlers
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export triangleFIR(float src, int len) =>
    filt = 0.0
    coef = 0.0
    sumcoef = 0.0
    for count = 1 to len
        if count <= len / 2
            coef := count
        else
            coef := len + 1 - count
        filt := filt + coef * nz(src[count - 1])
        sumcoef := sumcoef + coef
    filt := sumcoef != 0 ? filt / sumcoef : 0


// ----------
// Jurik MA
// ----------
jmapow(src, len) =>
    upperBand = src
    lowerBand = src
    
    // Volatility
    del1 = src - nz(upperBand[1], src)
    del2 = src - nz(lowerBand[1], src)
    volty = math.abs(del1) == math.abs(del2) ? 0 : math.max(math.abs(del1), math.abs(del2))
    
    // Incremental sum of Volty
    vSum = 0.0
    vSum := nz(vSum[1]) + 0.1 * (volty - nz(volty[10], volty))

    // Jurik used 65
    avgLen = 65
    avgVolty = ta.sma(vSum, avgLen)
    
    // Relative price volatility
    alen = (len - 1) / 2
    len1 = math.max(math.log(math.sqrt(alen)) / math.log(2) + 2, 0)
    pow1 = math.max(len1 - 2, 0.5)
    
    rVolty = avgVolty != 0 ? volty / avgVolty : 0
    if (rVolty > math.pow(len1, 1 / pow1))
        rVolty := math.pow(len1, 1 / pow1)
    if rVolty < 1
        rVolty := 1
    
    // Jurik Bands
    pow2 = math.pow(rVolty, pow1)
    len2 = math.sqrt(alen) * len1
    bet  = len2/(len2 + 1)
    kv   = math.pow(bet, math.sqrt(pow2))
    
    upperBand := del1 > 0 ? src : src - kv * del1
    lowerBand := del2 < 0 ? src : src - kv * del2

    pow2

// @function Jurik MA
// @param src Series to use
// @param len Filtering length
// @param phase JMA Phase
// @returns Filtered series
// Based on the reverse-engineered JMA documented by somebody called Igor: https://c.mql5.com/forextsd/forum/164/jurik_1.pdf
// Inspired by @everget implementation: https://www.tradingview.com/script/nZuBWW9j-Jurik-Moving-Average/
// Inspired by @gorx1 implementation: https://www.tradingview.com/script/gwzRz6tI-Jurik-Moving-Average/
// As JMA is a proprietary closed-source algorithm, every JMA implementation I've seen is based on the Igor's document
// Many of the implementations, however, are not true to the source
// As far as I know, this is the first correct JMA implementation on TradingView
// As the Igor's document itself is incomplete, however, there is some grey area still...
export jma(float src, int len, int phase = 50) =>
    jma = 0.0
    phaseRatio = phase < -100 ? 0.5 : phase > 100 ? 2.5 : phase / 100 + 1.5
    beta = 0.45 * (len - 1) / (0.45 * (len - 1) + 2)
    power = jmapow(src, len)
    alpha = math.pow(beta, power)
    
    // 1st stage - preliminary smoothing by adaptive EMA
    ma1 = 0.0
    ma1 := (1 - alpha) * src + alpha * nz(ma1[1])
    
    // 2nd stage - preliminary smoothing by Kalman filter
    det0 = 0.0
    det0 := (1 - beta) * (src - ma1) + beta * nz(det0[1])
    ma2 = ma1 + phaseRatio * det0
    
    // 3rd stage - final smoothing by Jurik adaptive filter
    det1 = 0.0
    det1 := math.pow(1 - alpha, 2) * (ma2 - nz(jma[1])) + math.pow(alpha, 2) * nz(det1[1])

    jma := nz(jma[1]) + det1
    jma


// ----------
// Filter Wrappers
// ----------

// @function Execute a particular Prefilter from the list
// @param type Prefilter type to use
// @param src Series to use
// @returns Filtered series
export doPrefilter(string type, float src) =>
    out = 0.0
    switch type
        "None" =>
            out := src
        "Simple Denoiser" =>
            out := hp2(src)
        "2 bar FIR" =>
            out := fir2(src)
        "3 bar FIR" =>
            out := fir3(src)
        "2,3 bar FIR" =>
            out := fir23(src)
        "2,3,4 bar FIR" =>
            out := fir234(src)
    
    out

// @function Execute a particular MA from the list
// @param type MA type to use
// @param src Series to use
// @param len Filtering length
// @returns Filtered series
export doMA(string type, float src, int len) =>
    out = 0.0
    switch type
        "None" =>
            out := src
        "SMA" =>
            out := sma(src, len)
        "RMA" =>
            out := rma(src, len)
        "EMA" =>
            out := ema(src, len)
        "HMA" =>
            out := hma(src, len)
        "VWMA" =>
            out := vwma(src, len)
        "2-pole Super Smoother" =>
            out := supers2(src, len)
        "3-pole Super Smoother" =>
            out := supers3(src, len)
        "Filt11" =>
            out := filt11(src, len)
        "Triangle Window" =>
            out := triangleFIR(src, len)
        "Hamming Window" =>
            out := hammingFIR(src, len)
        "Hann Window" =>
            out := hannFIR(src, len)
        "Lowpass" =>
            out := src - hp(src, len)
        "JMA" =>
            out := jma(src, len)
    out
