Library_Smoothers
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Koalems
// @version=5

// @description A library of smoothing algorithms.
//
// HOW TO USE: SAMPLE CODE: 
//
// Group8240 = 'MA 1: Settings'
// ma1Corrected                       = input.bool(false,    'Corrected',              group=Group8240)
// ma1Src                             = input.source(close,  'Source',                 group=Group8240, inline='MA1.1')
//
// ma1Type                            = input.string('SMMA', 'MA type',                group=Group8240, inline='MA1.2', options=['None', 'Corrected', 'EHMA', 'EMA', 'FRAMA', 'HMA', 'Jurik', 'LINREG', 'RMA', 'SMA', 'SMMA', 'Super Smoother', 'SWMA', 'TMA', 'TSF', 'VAWMA', 'VIDYA', 'VWMA', 'WMA', 'WWMA', 'ZLEMA'])
// ma1Length                          = input.int(15,        'Length',                 group=Group8240, inline='MA1.2', minval=1, step=5)
// ma1Timeframe                       = input.timeframe('',  'Res',                    group=Group8240, inline='MA1.2')
//
// Group8242 = 'MA 1: Settings: Extra'
// ma1FramaFast                       = input.int(2,         'FRAMA: Fast',            group=Group8242, inline='MA1.3', minval=1, step=1)
// ma1FramaSlow                       = input.int(200,       'Slow',                   group=Group8242, inline='MA1.3', minval=1, step=10)
// ma1JurikPhase                      = input.int(50,        'Jurik: Phase',           group=Group8242, inline='MA1.4', minval=-100, maxval=100, step=5)
// ma1JurikPower                      = input.float(2,       'Power',                  group=Group8242, inline='MA1.4', minval=0, step=0.25)
// ma1LinRegOffset                    = input.int(0,         'Lin. Reg.: Offset',      group=Group8242, inline='MA1.5', minval=0, step=1)
// ma1VwwmaStartingWeight             = input.int(1,         'VAWMA: Starting weight', group=Group8242, inline='MA1.6', minval=0, step=1)
// ma1VwwmaVolumeDefault              = input.int(0,         'Vol. default',           group=Group8242, inline='MA1.6', minval=0, step=1)
//
// ma1      = MAs.MaTypes(ma1Type, ma1Src, ma1Length, ma1FramaFast, ma1FramaSlow, ma1LinRegOffset, ma1JurikPhase, ma1JurikPower, ma1VwwmaStartingWeight, ma1VwwmaVolumeDefault, ma1Corrected)

library("Library_Smoothers", overlay=false)

SQRT2 = math.sqrt(2.0)

IsLenInvalid(simple int len, simple string name = 'len') =>
    if na(len)
        runtime.error("The '" + name + "' param cannot be " + str.tostring(len))
        true
    else if len < 0
        runtime.error("The '" + name + "' param cannot be negative.") 
        true       
    else if len == 0
        true
    else
        false

// @function CorrectedMA The strengths of the corrected Average (CA) is that the current value of the time series must exceed a the current volatility-dependent threshold, so that the filter increases or falls, avoiding false signals when the trend is in a weak phase.
// @param src Source
// @param length Length
// @returns The Corrected source.
export CorrectedMA(float Src, int Len) =>
    sma   = ta.sma(Src, Len)
    cma   = sma
    cma  := nz(cma[1], cma)
    v1    = ta.variance(Src, Len)
    v2    = math.pow(cma - sma, 2)
    k     = v2 < v1 ? 0 : 1 - v1 / v2
    cma  := cma + k * (sma - cma)
    cma

// Chande Momentum Oscillator
CmoF1(m)                => m >= 0.0 ?  m  : 0.0
CmoF2(m)                => m >= 0.0 ? 0.0 :  -m
CmoPercent(nom, div)    => 100 * nom / div
ChandeMomentumOscillator(float src, int length) =>
    momm = ta.change(src, length)
    m1   = CmoF1(momm)
    m2   = CmoF2(momm)
    sm1  = math.sum(m1, length)
    sm2  = math.sum(m2, length)
    out  = nz(CmoPercent(sm1 - sm2, sm1 + sm2))
    out

// @function EMA Exponential Moving Average. 
// @param src Source to act upon
// @param length Length of moving average
// @returns EMA of source
export EHMA(float src, simple int len) => ta.ema(2 * ta.ema(src, len / 2) - ta.ema(src, len), math.round(math.sqrt(len)))

// @function FRAMA Fractal Adaptive Moving Average
// @param src Source to act upon
// @param len Length of moving average
// @param FC Fast moving average
// @param SC Slow moving average
// @returns FRAMA of source
export FRAMA(float src, int len, int FC, int SC) =>
    len1 = len / 2
    w    = math.log(2 / (SC + 1)) / math.log(math.e)  // Natural logarithm (ln(2 / (SC + 1))) workaround

    H1   = ta.highest(high, len1)
    L1   = ta.lowest(low, len1)
    N1   = (H1 - L1) / len1

    H2   = ta.highest(high, len1)[len1]
    L2   = ta.lowest(low, len1)[len1]
    N2   = (H2 - L2) / len1

    H3   = ta.highest(high, len)
    L3   = ta.lowest(low, len)
    N3   = (H3 - L3) / len

    dimen1    = (math.log(N1 + N2) - math.log(N3)) / math.log(2)
    dimen     = N1 > 0 and N2 > 0 and N3 > 0 ? dimen1 : nz(dimen1[1])

    alpha1    = math.exp(w * (dimen - 1))
    oldalpha  = alpha1 > 1 ? 1 : (alpha1 < 0.01 ? 0.01 : alpha1)
    oldN      = (2 - oldalpha) / oldalpha

    N = (((SC - FC) * (oldN - 1)) / (SC - 1)) + FC

    alpha_    = 2 / (N + 1)
    alpha     = alpha_ < 2 / (SC + 1) ? 2 / (SC + 1) : (alpha_ > 1 ? 1 : alpha_)

    out       = 0.0
    out      := (1 - alpha) * nz(out[1]) + alpha * src
    out

// @function Jurik A low lag filter
// @param src Source
// @param length Length for smoothing
// @param phase Phase range is ±100
// @param power Mathematical power to use. Doesn't need to be whole numbers
// @returns Jurik of source
export Jurik(float src, int length, int phase, float power)=>
    jma         = 0.0
    e0          = 0.0
    e1          = 0.0
    e2          = 0.0
    phaseRatio  = phase < -100 ? 0.5 : phase > 100 ? 2.5 : phase / 100 + 1.5
    l1          = length - 1
    beta        = 0.45 * l1 / (0.45 * l1 + 2)
    alpha       = math.pow(beta, power)
    e0         := (1 - alpha) * src + alpha * nz(e0[1])
    e1         := (src - e0) * (1 - beta) + beta * nz(e1[1])
    e2         := (e0 + phaseRatio * e1 - nz(jma[1])) * math.pow(1 - alpha, 2) + math.pow(alpha, 2) * nz(e2[1])
    jma        := e2 + nz(jma[1])
    jma

// @function SMMA Smoothed moving average. Think of the SMMA as a hybrid of its better-known siblings — the simple moving average (SMA) and the exponential moving average (EMA).
// @param src Source
// @param length Length for smoothing
// @returns SMMA of source
export SMMA(float src, int len) =>
    sma   = ta.sma(src, len)
    smma  = 0.
    smma := na(smma[1]) ? sma : (smma[1] * (len - 1) + src) / len
    smma

// @function SuperSmoother
// @param src Source to smooth
// @param length Length to use
// @returns SuperSmoother of the source
export SuperSmoother(float src, int len) =>
    lambda    = math.pi * SQRT2 / len
    a1        = math.exp(-lambda)
    coeff2    = 2.0 * a1 * math.cos(lambda)
    coeff3    = -math.pow(a1, 2.0)
    coeff1    = 1.0 - coeff2 - coeff3
    filt1     = 0.0
    filt1    := coeff1 * (src + nz(src[1])) * 0.5 + coeff2 * nz(filt1[1]) + coeff3 * nz(filt1[2])
    filt1

// @function TMA Triangular Moving Average 
// @param src Source
// @param length Length to use
// @returns TMA of source
export TMA(float src, int len) => ta.sma(ta.sma(src, math.ceil(len / 2)), math.floor(len / 2) + 1)

// @function TSF Time Series Forecast. Uses linear regression.
// @param src Source
// @param length Length to use
// @returns TSF of source
export TSF(float src, int len) =>
    lrc     = ta.linreg(src, len, 0)
    lrc1    = ta.linreg(src, len, 1)
    lrs     = lrc - lrc1
    TSF     = ta.linreg(src, len, 0) + lrs
    TSF

// @function VIDYA Chande's Variable Index Dynamic Average. See http://www.fxcorporate.com/help/MS/NOTFIFO/i_Vidya.html 
// @param src Source
// @param length Length for CMO
// @returns VIDYA of source
export VIDYA(float src, simple int len) =>
    k = math.abs(ChandeMomentumOscillator(src, len)) / 100
    smoothingFactor = 2 / (len + 1)
    vidya=0.0
    vidya := nz(smoothingFactor * k * src) + (1 - smoothingFactor * k) * nz(vidya[1])

// @function VAWMA = VWMA and WMA combined. Simply put, this attempts to determine the average price per share over time weighted heavier for recent values. Uses a triangular algorithm to taper off values in the past (same as WMA does).
// @param src Source
// @param len Length
// @param volumeDefault The default value to use when a chart has no volume. 
// @returns The VAWMA of the source.
export VAWMA(
  series float  src,
  simple int    len,
  simple int    startingWeight=1,
  simple float  volumeDefault =na
  ) =>

    var float   notAvailable = na
    if IsLenInvalid(len)
        notAvailable
    else  
        sum = 0.0
        vol = 0.0
        last = len - 1
        for i = 0 to last
            s = src[i]
            v = volume[i]

            if na(v) and not na(volumeDefault)
                v := volumeDefault

            if not na(s) and not na(v)
                m    = last - i + startingWeight
                v   *= m
                vol += v
                sum += s * v

        ma = vol == 0 ? na : sum / vol
        ma

// @function WWMA Welles Wilder Moving Average
// @param src Source
// @param length Length
// @returns The WWMA of the source
export WWMA(float src, int len) =>
    wwalpha = 1 / len
    WWMA    = 0.0
    WWMA    := wwalpha * src + (1 - wwalpha) * nz(WWMA[1])

// 
// @function ZLEMA Zero Lag Expotential Moving Average
// @param src Source
// @param length
// @param 
// @returns The ZLEMA of the source
export ZLEMA(float src, int len) =>
    lag       = len / 2 == math.round(len / 2) ? len / 2 : (len - 1) / 2
    EMAData   = src + src - src[lag]
    ZLEMA     = ta.ema(EMAData, len)
    ZLEMA

// @function Performs the specified moving average
// @param mode Name of moving average
// @param src the source to apply the MA type
// @param the length for the MA
// @param fastMA FRAMA fast moving average
// @param slowMA FRAMA slow  moving average
// @param offset Linear regression offset
// @param phase Jurik phase
// @param power Jurik power
// @param startingWeight VAWMA starting weight 
// @param volumeDefault VAWMA default volume
// @param Corrected 
// @returns The MA smoothed source
export SmootherType(
  simple string mode,
  float         src,
  simple int    len,
  simple int    fastMA          = 0,
  simple int    slowMA          = 0,
  simple int    offset          = 0,
  simple int    phase           = 0,
  simple float  power           = 0,
  simple int    startingWeight  = 1,
  simple float  volumeDefault   = na,
  simple bool   Corrected       = false
  ) =>

    float smoother = switch mode
        'Corrected'         =>   CorrectedMA(        src, len)
        'EHMA'              =>          EHMA(        src, len)
        'EMA'               =>        ta.ema(        src, len)
        'FRAMA'             =>         FRAMA(        src, len, fastMA, slowMA)
        'HMA'               =>        ta.hma(        src, len)
        'Jurik'             =>         Jurik(        src, len, phase,  power)
        'LINREG'            =>     ta.linreg(        src, len, offset)
        'None'              =>                       src                        // NONE !!!!!
        'RMA'               =>        ta.rma(        src, len)
        'SMA'               =>        ta.sma(        src, len)
        'SMMA'              =>          SMMA(        src, len)
        'Super Smoother'    => SuperSmoother(        src, len)
        'SWMA'              =>       ta.swma(        src)
        'TMA'               =>           TMA(        src, len)
        'TSF'               =>           TSF(        src, len)
        'VAWMA'             =>         VAWMA(        src, len, startingWeight, volumeDefault)
        'VIDYA'             =>         VIDYA(        src, len)
        'VWMA'              =>       ta.vwma(        src, len)
        'WMA'               =>        ta.wma(        src, len)
        'WWMA'              =>          WWMA(        src, len)
        'ZLEMA'             =>         ZLEMA(        src, len)
        =>
            runtime.error("No matching smoother type found.")
            float(na)

    if Corrected
        smoother := CorrectedMA(src, len)
    smoother
