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

// @version=5
library('MovingAverages', true)

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

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// VAWMA = VWMA and WMA combined.
// Simply put, this attempts to determine the average price per share over time weighted heavier for recent values.
// Uses triangular algorithm to taper off values in the past (same as WMA does).
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////
// @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 len The number of bars to measure with.
// @param src The series to measure from.  Default is 'hlc3'.
// @param volumeDefault The default value to use when a chart has no (N/A) volume. 
// @returns The volume adjusted triangular weighted moving average of the series.
export vawma(
  simple int len,
  series float src = hlc3,
  simple float volumeDefault = na,
  simple int startingWeight = 1) =>
    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
        vol == 0 ? na : sum/vol
///////////////////////////////////////////////////

///////////////////////////////////////////////////
// @function Coefficient Moving Avereage (CMA) is a variation of a moving average that can simulate SMA or WMA with the advantage of previous data.
// @param n The number of bars to measure with.
// @param D The series to measure from.  Default is 'close'.
// @param C The coefficient to use when averaging. 0 behaves like SMA, 1 behaves like WMA.
// @param compound When true (default is false) will use a compounding method for weighting the average.
export cma(
  simple int n,
  series float D = close,
  simple float C = 1,
  simple bool compound = false) =>
    var float notAvailable = na
    if isLenInvalid(n, 'n')
        notAvailable
    else 
        var float result = na
        sum = 0.0
        weight = 1.0
        weightTotal = 0.0
        for m = 1 to n
            s = D[n - m]
            if not na(s)
                sum := sum + s * weight
                weightTotal += weight
                if compound
                    weight *= 1 + C
                else
                    weight += C

        prev = result[n]
        if na(prev)
            result := sum / weightTotal
        else
            result := (prev + sum) / (weightTotal + 1)
///////////////////////////////////////////////////


///////////////////////////////////////////////////
// @function Same as ta.ema(src,len) but properly ignores NA values.
// @param len The number of samples to derive the average from.
// @param src The series to measure from.  Default is 'close'.
export ema(
  simple int len,
  series float src = close) =>
    var float notAvailable = na
    if isLenInvalid(len)
        notAvailable
    else 
        var float alpha = 2 / (len + 1)
        var float a1 = 1 - alpha
        var float s = na
        var float ema = na
        if not na(src)
            s := src
        ema := alpha * s + a1 * nz(ema)
///////////////////////////////////////////////////

///////////////////////////////////////////////////
// @function Same as ta.wma(src,len) but properly ignores NA values.
// @param len The number of samples to derive the average from.
// @param src The series to measure from.  Default is 'close'.
// @param startingWeight The weight to begin with when calculating the average.  Higher numbers will decrease the bias.
export wma(
  simple int len,
  series float src = close,
  simple int startingWeight = 1) =>
    var float notAvailable = na
    if isLenInvalid(len)
        notAvailable
    else
        sum = 0.0
        total = 0
        last = len - 1
        for i = 0 to last
            s = src[i]
            if not na(s)
                m = last - i + startingWeight // triangular multiple
                total += m
                sum += s * m
        total == 0 ? na : sum/total
///////////////////////////////////////////////////

///////////////////////////////////////////////////
// @function Same as ta.vwma(src,len) but properly ignores NA values.
// @param len The number of bars to measure with.
// @param src The series to measure from.  Default is 'hlc3'.
// @param volumeDefault The default value to use when a chart has no (N/A) volume. 
export vwma(
  simple int len,
  series float src = close,
  simple float volumeDefault = na) =>
    var float notAvailable = na
    if isLenInvalid(len)
        notAvailable
    else
        var sum = 0.0
        var vol = 0.0
        float datum = na

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

        if not na(src) and not na(v)
            vol += v
            datum := src * v
            sum += datum
        else
            v := na

        vStart = v[len]
        dStart = datum[len]
        if not na(vStart)
            vol -= vStart
        if not na(dStart)
            sum -= dStart

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

SMA = 'SMA', EMA = 'EMA', WMA = 'WMA', CMA = 'CMA', VWMA = 'VWMA', VAWMA = 'VAWMA'

///////////////////////////////////////////////////
// @function Generates a moving average based upon a 'type'.
// @param type The type of moving average to generate.  Values allowed are: SMA, EMA, WMA, VWMA and VAWMA.
// @param len The number of bars to measure with.
// @param src The series to measure from.  Default is 'close'.
// @returns The moving average series requested.
export get(
  simple string type,
  simple int len,
  series float src = close) =>
    switch type
        WMA => wma(len, src)
        EMA => ema(len, src)
        VWMA => vwma(len, src)
        VAWMA => vawma(len, src)
        CMA => cma(len, src)
        SMA => ta.sma(src, len)
        =>
            runtime.error("No matching MA type found.")
            ta.sma(src, len)
///////////////////////////////////////////////////

// Demo
plot(ema(20), 'ema(20)', color.red)
plot(wma(20), 'wma(20)', color.yellow)
plot(vwma(20), 'vwma(20)', color.blue)
plot(vawma(20), 'vawma(20)', color.purple)