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

//@version=5

// @description Exotic or Interesting Moving Averages Lib.
library("MovingAverages", overlay = true)

emaAlpha(src, alpha) =>
    // https://nullbeans.com/how-to-calculate-the-exponential-moving-average-ema/
    // alpha value must be between 0 and 1
    ema = 0.0
    // update happens on demand at each iteration/bar, calculate current EMA based
    // on prior bar thus we don't use any looping constructs.
    ema := na(ema[1]) ? src : alpha * src + (1 - alpha) * nz(ema[1])
    ema

emaAlphaSmoothing(src, alpha, nSmooth) =>
    if nSmooth <= 0
        src
    else
        _ema = src
        for i = 1 to nSmooth
            // warning shown, but seems to work okay.
            _ema := emaAlpha(_ema, alpha)
        _ema

// @function Calculates a variation of the EMA by specifying a custom alpha value.
// @param src a float series to get the EMA for, e.g. close, hlc3, etc.
// @param alpha a value between 0 (ideally greater,  to get any MA!) and 1. Closer
//        to one makes it more responsive, and choppier.
// @param nSmooth Just applies the same alpha and EMA to the last Alpha-EMA output.
//        A value between 0 and 10 (just keeping a a reasonable bound). The idea is
//        you can first use a reasonably high alpha, then smooth it out. Default 0,
//        no further smoothing.
// @returns MA series.
export alphaConfigurableEma(float src, float alpha = 0.5, int nSmooth = 0) =>
	if alpha < 0 or alpha > 1
		runtime.error("Alpha must be 0 and 1, more than zero to get a non-zero series!")
	if nSmooth < 0 or nSmooth > 10
		runtime.error("nSmooth must be between 0 and 10, inclusive")
	ma = emaAlpha(src = src, alpha = alpha)
	ma := emaAlphaSmoothing(src = ma, alpha = alpha, nSmooth = nSmooth)
	ma

// @function Calculates fixed bands around a series, can be any series, though the intent
//           here is for MA series.
// @param src a float series.
// @param multiplier a value greater than or equal to 0 (ideally greater,  to get any MA!),
//        determines the width of the bands. Start with small float values, or it may go
//        beyond the scale, e.g. 0.005.
// @returns a 2-tuple of (upBand, downBand)
export bands(float src, float multiplier) =>
	if multiplier < 0
		runtime.error("Band Multiplier must be 0 or more, e.g. 0.003. 0, is valid, but would produce zero series!")
	offset = src * multiplier
	upBand = src + offset
	downBand = src - offset
	[upBand, downBand]

// sample code
ema = alphaConfigurableEma(src = close, alpha = 0.3, nSmooth = 2)
[upBand, downBand] = bands(ema, multiplier = 0.006)

plot(ema, linewidth = 2, color = color.maroon)
plot(upBand, linewidth = 1, color = color.gray)
plot(downBand, linewidth = 1, color = color.gray)
