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

//@version=5

// import gliderfund/StapleIndicators/1 as ind

// @description This Library provides some common indicators commonly referenced from other studies in Pine Script
library(title = "StapleIndicators", overlay = true)




// #############################################
// #############################################
//            SQUEEZE
// #############################################
// #############################################

// @function            Volatility Squeeze
// @param bbSrc         (Optional) Bollinger Bands Source. By default close
// @param bbPeriod      (Optional) Bollinger Bands Period. By default 20
// @param bbDev         (Optional) Bollinger Bands Standard Deviation. By default 2.0
// @param kcSrc         (Optional) Keltner Channel Source. By default close
// @param kcPeriod      (Optional) Keltner Channel Period. By default 20
// @param kcATR         (Optional) Keltner Channel ATR Multiplier. By default 1.5
// @param signalPeriod  (Optional) Keltner Channel ATR Multiplier. By default 1.5
// @returns             [sqzOn, sqzOsc, sqzSignal]
export squeeze(series float bbSrc = close, simple int bbPeriod = 20, simple float bbDev = 2.0, series float kcSrc = close, simple int kcPeriod = 20, simple float kcATR = 1.5, simple int signalPeriod = 13) =>
    // Bollinger Bands
    [bbBasis, bbHigh, bbLow] = ta.bb(bbSrc, bbPeriod, bbDev)
    // Keltner Channel (simplified version from Linda Raschke)
    kcBasis = ta.ema(kcSrc, kcPeriod)
    kcDev   = kcATR * ta.atr(kcPeriod)
    kcHigh  = kcBasis + kcDev
    kcLow   = kcBasis - kcDev
    // Squeeze
    sqzOn   = (bbLow > kcLow) and (bbHigh < kcHigh)
    sqzOsc  = bbHigh - kcHigh
    // Signal Line
    sqzSignal = ta.ema(sqzOsc, signalPeriod)
    aboveSignal = sqzOsc >  sqzSignal
    belowSignal = sqzOsc <= sqzSignal

    // Result
    [sqzOn, sqzOsc, sqzSignal, aboveSignal, belowSignal]

// End of Function




// #############################################
// #############################################
//            ADX: AVERAGE DIRECTIONAL INDEX
// #############################################
// #############################################

// @function            ADX: Average Directional Index
// @param diPeriod      (Optional) Directional Indicator Period. By default 14
// @param adxPeriod     (Optional) ADX Smoothing. By default 14
// @param signalPeriod  (Optional) Signal Period. By default 13
// @param adxTier1      (Optional) ADX Tier #1 Level. By default 20
// @param adxTier2      (Optional) ADX Tier #2 Level. By default 15
// @param adxTier3      (Optional) ADX Tier #3 Level. By default 10
// @returns             [oscillator, signal, adxOff, adx1, adx2, adx3]
export adx(simple int diPeriod = 14, simple int adxPeriod = 14, simple int signalPeriod = 13, simple int adxTier1 = 20,  simple int adxTier2 = 15, simple int adxTier3 = 10) =>
    // ADX is a lagging indicator that helps us to read if we are in trending or ranging market
    // Below 20 signals range
    // Above 20 signals trend
    [_, _, oscillator] = ta.dmi(diPeriod, adxPeriod)

    // Signal
    signal = ta.ema(oscillator, signalPeriod)

    // Conditions
    adxOff =  oscillator >  adxTier1
    adx1   = (oscillator <= adxTier1) and (oscillator >  adxTier2)
    adx2   = (oscillator <= adxTier2) and (oscillator >  adxTier3)
    adx3   = (oscillator <= adxTier3)

    // Result
    [oscillator, signal, adxOff, adx1, adx2, adx3]

// End of Function




// #############################################
// #############################################
//            SMAs: SIMPLE MOVING AVERAGES
// #############################################
// #############################################

// @function            Delivers a set of frequently used Simple Moving Averages
// @param srcMa         (Optional) MA Source. By default 'close'
// @returns             [sma3, sma5, sma6, sma7, sma8, sma10, sma20, sma25, sma30, sma50, sma100, sma200, smaAllTime]
export smaPreset(series float srcMa = close) =>
    // SMAs
    sma3   = ta.sma(srcMa, 3)
    sma5   = ta.sma(srcMa, 5)
    sma6   = ta.sma(srcMa, 6)
    sma7   = ta.sma(srcMa, 7)
    sma8   = ta.sma(srcMa, 8)
    sma10  = ta.sma(srcMa, 10)
    sma20  = ta.sma(srcMa, 20)
    sma25  = ta.sma(srcMa, 25)
    sma30  = ta.sma(srcMa, 30)
    sma50  = ta.sma(srcMa, 50)
    sma100 = ta.sma(srcMa, 100)
    sma200 = ta.sma(srcMa, 200)
    smaAllTime = ta.cum(srcMa) / (ta.barssince(barstate.isfirst) + 1)

    // Result
    [sma3, sma5, sma6, sma7, sma8, sma10, sma20, sma25, sma30, sma50, sma100, sma200, smaAllTime]

// End of Function




// #############################################
// #############################################
//            EMAs: EXPONENTIAL MOVING AVERAGES
// #############################################
// #############################################

// @function            Delivers a set of frequently used Exponential Moving Averages
// @param srcMa         (Optional) MA Source. By default 'close'
// @returns             [ema3, ema5, ema8, ema9, ema13, ema18, ema21, ema34, ema50, ema55, ema89, ema100, ema144, ema200, ema377]
export emaPreset(series float srcMa = close) =>
    // EMAs
    ema3   = ta.ema(srcMa, 3)
    ema5   = ta.ema(srcMa, 5)
    ema8   = ta.ema(srcMa, 8)
    ema9   = ta.ema(srcMa, 9)
    ema13  = ta.ema(srcMa, 13)
    ema18  = ta.ema(srcMa, 18)
    ema21  = ta.ema(srcMa, 21)
    ema34  = ta.ema(srcMa, 34)
    ema50  = ta.ema(srcMa, 50)
    ema55  = ta.ema(srcMa, 55)
    ema89  = ta.ema(srcMa, 89)
    ema100 = ta.ema(srcMa, 100)
    ema144 = ta.ema(srcMa, 144)
    ema200 = ta.ema(srcMa, 200)
    ema377 = ta.ema(srcMa, 377)

    // Result
    [ema3, ema5, ema8, ema9, ema13, ema18, ema21, ema34, ema50, ema55, ema89, ema100, ema144, ema200, ema377]

// End of Function




// #############################################
// #############################################
//            MA SELECTION TOOL
// #############################################
// #############################################

// @function            Filters and outputs the selected MA
// @param ma            (Optional) MA text. By default 'Ema-21'
// @param srcMa         (Optional) MA Source. By default 'close'
// @returns             maSelected
export maSelect(simple string ma = "Ema-21", series float srcMa = close) =>

    float maSelected = switch ma
        "Sma-3" => ta.sma(srcMa, 3)
        "Ema-3" => ta.ema(srcMa, 3)
        "Sma-5" => ta.sma(srcMa, 5)
        "Ema-5" => ta.ema(srcMa, 5)
        "Sma-6" => ta.sma(srcMa, 6)
        "Sma-7" => ta.sma(srcMa, 7)
        "Sma-8" => ta.sma(srcMa, 8)
        "Ema-8" => ta.ema(srcMa, 8)
        "Ema-9" => ta.ema(srcMa, 9)
        "Sma-10" => ta.sma(srcMa, 10)
        "Ema-13" => ta.ema(srcMa, 13)
        "Ema-18" => ta.ema(srcMa, 18)
        "Sma-20" => ta.sma(srcMa, 20)
        "Ema-21" => ta.ema(srcMa, 21)
        "Sma-25" => ta.sma(srcMa, 25)
        "Sma-30" => ta.sma(srcMa, 30)
        "Ema-34" => ta.ema(srcMa, 34)
        "Sma-50" => ta.sma(srcMa, 50)
        "Ema-50" => ta.ema(srcMa, 50)
        "Ema-55" => ta.ema(srcMa, 55)
        "Ema-89" => ta.ema(srcMa, 89)
        "Sma-100" => ta.sma(srcMa, 100)
        "Ema-100" => ta.ema(srcMa, 100)
        "Ema-144" => ta.ema(srcMa, 144)
        "Sma-200" => ta.sma(srcMa, 200)
        "Ema-200" => ta.ema(srcMa, 200)
        "Ema-377" => ta.ema(srcMa, 377)
        "Sma All-Time" => ta.cum(srcMa) / (ta.barssince(barstate.isfirst) + 1)
        => na

    // Result
    maSelected

// End of Function




// #############################################
// #############################################
//            ADAPTATIVE PERIOD
// #############################################
// #############################################

// @function             Adaptative Period
// @param modeAdaptative (Optional) Adaptative Mode. By default 'Average'
// @param src            (Optional) Source. By default 'close'
// @param maxLen         (Optional) Max Period. By default '60'
// @param minLen         (Optional) Min Period. By default '4'
// @returns              periodAdaptative
export periodAdapt(simple string modeAdaptative = "Average", series float src = close, simple int maxLen = 60, simple int minLen = 4) =>
    // Credit to EmpiricalFX for his implementation of Cosine, In-Phase & Quadrature IFM [Ehlers] (https://www.tradingview.com/script/1LF6cPby-Cosine-In-Phase-Quadrature-IFM-Ehlers/)

    // General Vars
    lengthIQ  = 0.0
    lengthCos = 0.0
    P = src - src[7]

    // Conditions
    modeIQ  = modeAdaptative == "In-Phase & Quadrature"
    modeCos = modeAdaptative == "Cosine"
    modeAvg = modeAdaptative == "Average"  // Averages Cosine and I-Q

    // I-Q IFM
    iMult = 0.635, qMult = 0.338
    inPhase = 0.0, quadrature = 0.0
    deltaIQ = 0.0, instIQ = 0.0
    re = 0.0, im = 0.0, V = 0.0

    if(modeIQ or modeAvg)
        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])

        deltaIQ := re != 0.0 ? math.atan(im / re) : deltaIQ

        for i = 0 to maxLen
            V := V + deltaIQ[i]
            instIQ := (V > 2 * math.pi) and (instIQ == 0.0) ? i : instIQ

        instIQ := instIQ == 0.0 ? nz(instIQ[1]) : instIQ

        lengthIQ := 0.25 * instIQ + 0.75 * nz(lengthIQ[1], 1)


    // COSINE IFM
    s2 = 0.0,       s3 = 0.0
    v2 = 0.0,       v4 = 0.0
    deltaCos = 0.0, instCos = 0.0

    if(modeCos or modeAvg)
        s2 := 0.2 * (P[1] + P) * (P[1] + P) + 0.8 * nz(s2[1])
        s3 := 0.2 * (P[1] - P) * (P[1] - P) + 0.8 * nz(s3[1])
        v2 := s2 != 0 ? math.sqrt(s3 / s2) : v2
        deltaCos := s3 != 0 ? 2 * math.atan(v2) : deltaCos

        for i = 0 to maxLen
            v4 := v4 + deltaCos[i]
            instCos := (v4 > 2 * math.pi) and (instCos == 0.0) ? i - 1 : instCos

        instCos := instCos == 0.0 ? instCos[1] : instCos

        lengthCos := 0.25 * instCos + 0.75 * nz(lengthCos[1], 1)


    // Adaptative result
    int periodAdaptative = switch
        modeIQ   => math.round(lengthIQ)
        modeCos  => math.round(lengthCos)
        modeAvg  => math.round((lengthCos + lengthIQ) / 2.0)

    periodAdaptative := math.max(periodAdaptative, minLen)


    // Result
    periodAdaptative

// End of Function




// #############################################
// #############################################
//            AZLEMA: ADAPTATIVE ZERO-LAG EMA
// #############################################
// #############################################

// @function             Azlema: Adaptative Zero-Lag Ema
// @param modeAdaptative (Optional) Adaptative Mode. By default 'Average'
// @param srcMa          (Optional) MA Source. By default 'close'
// @returns              azlema
export azlema(simple string modeAdaptative = "Average", series float srcMa = close) =>

    // Vars
    maxLen = 60,  minLen = 4
    azlema = 0.0

    // Call the adaptative function
    periodAdaptative = periodAdapt(modeAdaptative, srcMa, maxLen, minLen)

    // Azlema
    alphaZlema = 2 / (periodAdaptative + 1)
    azlema := (alphaZlema * srcMa) + ((1 - alphaZlema) * nz(azlema[1]))

    // Result
    azlema

// End of Function




// #############################################
// #############################################
//            SSMA: Smooth Simple MA
// #############################################
// #############################################

// @function             SSMA: Smooth Simple MA
// @param lsmaVar        Linear Regression Curve.
// @param srcMa          (Optional) MA Source. By default 'close'
// @param periodMa       (Optional) MA Period. By default '13'
// @returns              ssma
export ssma(series float lsmaVar, series float srcMa = close, simple int periodMa = 13) =>

    // Factors
    a1 = math.exp(-1.414 * 3.14159 / periodMa)
    b1 = 2 * a1 * math.cos(1.414 * 3.14159 / periodMa)
    c2 = b1
    c3 = (-a1) * a1
    c1 = 1 - c2 - c3

    // Ssma
    ssma = (c1 * (srcMa + nz(srcMa[1])) / 2) + (c2 * nz(lsmaVar[1])) + (c3 * nz(lsmaVar[2]))

    // Result
    ssma

// End of Function




// #############################################
// #############################################
//            JURIK VOLATILITY FACTOR
// #############################################
// #############################################

// @function            Jurik Volatility Factor
// @param srcMa         (Optional) MA Source. By default 'close'
// @param periodMa      (Optional) MA Period. By default '7'
// @returns             [del1, del2, kv, len, pow2]
export jvf(series float srcMa = close, simple int periodMa = 7) =>
    // Credit to gorx1 for his implementation of the volatility factor (https://www.tradingview.com/script/gwzRz6tI-Jurik-Moving-Average/)

    // Vars
    upBand = srcMa, lowBand = srcMa  // Upper and Lower Bands
    sumVolat = 0.0  // Incremental Volatility Sum
    avgLen   = 65   // Period of Average Volatility
    avgVolat = 0.0  // Average Volatility

    // Distance between price and bands
    del1 = math.abs(srcMa - nz(upBand[1]))
    del2 = math.abs(srcMa - nz(lowBand[1]))

    // Price volatility
    priceVolat = del1 == del2 ? 0 : math.max(del1, del2)

    // Incremental Volatility Sum
    sumVolat := nz(sumVolat[1]) + (priceVolat - priceVolat[10]) / 10

    // Average Volatility
    tempVolat = ta.sma(sumVolat, avgLen) // To avoid pine compiler warning of inconsistency
    avgVolat := bar_index <= avgLen ? nz(avgVolat[1]) + 2.0 * (sumVolat - nz(avgVolat[1])) / (avgLen + 1) : tempVolat

    // Periodic factor
    len  = 0.5 * (periodMa - 1)
    len1 = math.max(math.log(math.sqrt(len)) / math.log(2.0) + 2, 0)

    // Power of Relative Volatility
    pow1 = math.max(len1 - 2, 0.5)

    // Relative Price Volatility
    relVolat = avgVolat > 0 ? priceVolat / avgVolat : 0
    relVolat :=
      relVolat > math.pow(len1, 1 / pow1) ? math.pow(len1, 1 / pow1) :
      relVolat < 1 ? 1 : relVolat

    // Volatility Factor
    pow2 = math.pow(relVolat, pow1)
    len2 = math.sqrt(len) * len1
    bet  = len2 / (len2 + 1)
    kv   = math.pow(bet, math.sqrt(pow2))


    // Result
    [del1, del2, kv, len, pow2]

// End of Function




// #############################################
// #############################################
//            JURIK BANDS
// #############################################
// #############################################

// @function            Jurik Bands
// @param srcMa         (Optional) MA Source. By default 'close'
// @param periodMa      (Optional) MA Period. By default '7'
// @returns             [upBand, lowBand]
export jBands(series float srcMa = close, simple int periodMa = 7) =>
    // Credit to gorx1 for his integrated implementation of the bands along with the volatility factor (https://www.tradingview.com/script/gwzRz6tI-Jurik-Moving-Average/)

    // Retrieve the factors
    [del1, del2, kv, _, _] = jvf(srcMa, periodMa)

    // Jurik Bands
    upBand = (bar_index + 1) == 1 or (del1 < 0) ? srcMa : srcMa + kv * del1

    lowBand = (bar_index + 1) == 1 or (del2 < 0) ? srcMa : srcMa - kv * del2


    // Result
    [upBand, lowBand]

    // End of Function




// #############################################
// #############################################
//            JMA: JURIK MOVING AVERAGE
// #############################################
// #############################################

// @function            Jurik MA (JMA)
// @param srcMa         (Optional) MA Source. By default 'close'
// @param periodMa      (Optional) MA Period. By default '7'
// @param phase         (Optional) Phase. By default '50'
// @returns             jma
export jma(series float srcMa = close, simple int periodMa = 7, simple float phase = 50) =>
    // Credit to gorx1 for his integrated implementation of the JMA along with the volatility factor (https://www.tradingview.com/script/gwzRz6tI-Jurik-Moving-Average/)

    // Retrieve the factors
    [_, _, _, len, pow2] = jvf(srcMa, periodMa)

    // Vars
    stage1 = 0.0, stage2 = 0.0, stage3 = 0.0  // JMA stages
    factor2 = 0.0, factor3 = 0.0  // Factors in Smoothing stages

    // Phase Ratio
    phaseRatio =
      phase < -100 ? 0.5 :
      phase >  100 ? 2.5 : phase / 100 + 1.5

    // Periodic Factor
    beta = 0.45 * (len - 1) / (0.45 * (len - 1) + 2)

    // Dynamic Factor
    alpha = math.pow(beta, pow2)

    // Stage #1: Preliminary smoothing by an Adaptative Ema
    stage1 := (1 - alpha) * srcMa + alpha * nz(stage1[1])

    // Stage #2: One more smoothing using Kalman Filter
    factor2 := (srcMa - stage1) * (1 - beta) + beta * nz(factor2[1])
    stage2 := stage1 + phaseRatio * factor2

    // Stage #3: Final smoothing by Jurik Adaptative Filter
    factor3 := (stage2 - nz(stage3[1])) * math.pow(1 - alpha, 2) + math.pow(alpha, 2) * nz(factor3[1])
    stage3 := nz(stage3[1]) + factor3

    jma = stage3

    // Result
    jma

// End of Function




// #############################################
// #############################################
//            MA CUSTOM
// #############################################
// #############################################

// @function            Creates a custom Moving Average
// @param ma            (Optional) MA text. By default 'Ema'
// @param srcMa         (Optional) MA Source. By default 'close'
// @param periodMa      (Optional) MA Period. By default '13'
// @param lrOffset      (Optional) Linear Regression Offset. By default '0'
// @param almaOffset    (Optional) Alma Offset. By default '0.85'
// @param almaSigma     (Optional) Alma Sigma. By default '6'
// @param jmaPhase      (Optional) JMA Phase. By default '50'
// @param azlemaMode    (Optional) Azlema Adaptative Mode. By default 'Average'
// @returns             maTF
export maCustom(simple string ma = "Ema", series float srcMa = close, simple int periodMa = 13, simple int lrOffset = 0, simple float almaOffset = 0.85, simple int almaSigma = 6, simple float jmaPhase = 50, simple string azlemaMode = "Average") =>

    smaVar = ta.sma(srcMa, periodMa)

    smmaVar = 0.0
    smmaVar :=
      ma != "Smma" ? float(na) :
      (na(smmaVar[1]) ? smaVar :
      (smmaVar[1] * (periodMa - 1) + srcMa) / periodMa)

    mavVar = 0.0
    mavVar := ma == "Mav" ? nz(mavVar[1]) + (srcMa - nz(mavVar[1])) / periodMa : float(na)

    float maSelected = switch ma
        "Sma"  => smaVar  // Simple MA
        "Ema"  => ta.ema(srcMa, periodMa)  // Exponential MA
        "Dema" => 2 * ta.ema(srcMa, periodMa) - ta.ema(ta.ema(srcMa, periodMa), periodMa)  // Double Exponential MA (by Patrick G. Mulloy)
        "Tema" => 3 * (ta.ema(srcMa, periodMa) - ta.ema(ta.ema(srcMa, periodMa), periodMa)) + ta.ema(ta.ema(ta.ema(srcMa, periodMa), periodMa), periodMa)  // Triple Exponential MA (by Patrick G. Mulloy)
        "Wma"  => ta.wma(srcMa, periodMa)  // Weighted MA
        "Vwma" => ta.vwma(srcMa, periodMa)  // Volume Weighted MA (by Buff P. Dormeier)
        "Hma"  => ta.hma(srcMa, periodMa)  // Hull MA (by Alan Hull). Previously: wma(2 * wma(src, periodMa / 2) - wmaVar
        "Lsma" => ta.linreg(srcMa, periodMa, lrOffset)  // Least Squares MA (Moving Linear Regression)
        "Rma"  => ta.rma(srcMa, periodMa) // Relative MA  (by J. Welles Wilder)
        "Alma" => ta.alma(srcMa, periodMa, almaOffset, almaSigma)  // Arnaud Legoux MA (by Arnaud Legoux and Dimitris Kouzis-Loukas)
        "Tma"  => ta.sma(smaVar, periodMa)  // Triangular MA (generalized by John F. Ehlers)
        "Dwma" => ta.wma(ta.wma(srcMa, periodMa), periodMa)  // Double Weighted (Linear) MA
        "Smma" => smmaVar  // Smoothed MA
        "Mav" =>  mavVar // Modified MA (used by Dinapoli in the Preferred Stochastic)
        "Zlema" => ta.ema(srcMa + (srcMa - srcMa[(periodMa - 1) / 2]), periodMa)  // Zero Lag Exponential MA (by John F. Ehlers and Ric Way).
        "Azlema" => azlema(azlemaMode, srcMa)  // Adaptative Zero Lag Ema
        "Jma" => jma(srcMa, periodMa, jmaPhase)  // Jurik MA (by Mark Jurik)
        "Ssma" => ssma(ta.linreg(srcMa, periodMa, lrOffset), srcMa, periodMa)  // Smooth Simple MA
        "Swma" => ta.swma(srcMa)  // Symmetrically weighted MA with fixed length: 4. Weights: [1/6, 2/6, 2/6, 1/6]
        "All-Time" => ta.cum(srcMa) / (ta.barssince(barstate.isfirst) + 1)  // All-time MA comprising the whole history of that market
        =>
            runtime.error("MA not yet supported")
            float(na)

    // Result
    maSelected

// End of Function
