Variety N-Tuple Moving Averages w/ Variety Stepping [Loxx]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © loxx

//@version=5
indicator("Variety N-Tuple Moving Averages w/ Variety Stepping [Loxx]", 
     shorttitle='VNTMAVS [Loxx]', 
     overlay = true, 
     timeframe="", 
     timeframe_gaps = true)

import loxx/loxxexpandedsourcetypes/4

greencolor = #2DD204
redcolor = #D2042D 
lightgreencolor = #96E881 
lightredcolor =  #DF4F6C

fact(int n)=>
    float out = 1
    for i = 1 to n 
        out *= i
    out

// TradingView user @lejmer was a key player in making this function come to life
nema(string nematype, float src, int per, int dpth, int mxdpth) =>
    var float[] coeff = array.new<float>(mxdpth + 1, 0.)
    if barstate.isfirst
        int nemadpth = math.max(math.min(dpth, mxdpth), 1)
        for k = 0 to dpth 
            array.set(coeff, k, nz(fact(dpth) / (fact(dpth - k) * fact(k)), 1))
            
    var float[] workarr = array.new<float>(dpth + 1, 0)
    array.set(workarr, 0, src)
    int sign = 1
    float nema = 0.
    float alpha = nematype == "EMA" ? 2.0 / (1.0 + per) : 1 / per
    for k = 0 to dpth - 1 
        temp = array.get(workarr, k + 1) + alpha * (array.get(workarr, k) - array.get(workarr, k + 1))
        array.set(workarr, k + 1, temp)
        nema += temp * sign * array.get(coeff, k + 1)
        sign *= -1
    nema

adaptvieATR(float src, int per)=>
    float mper = (per > 1) ? per : 1
    float mfast = math.max(mper / 2.0, 1)
    float mslow = mper * 5
    float mperDiff = mslow - mfast
    
    float noise = 0., float aatr = 0.
    float diff = math.abs(src - nz(src[1]))
    float signal = math.abs(src - nz(src[mper]))
    noise := nz(noise[1]) + diff - nz(diff[mper])
    float avgper = (noise != 0) ? (signal / noise) * mperDiff + mfast : mper
    aatr := nz(aatr[1]) + (2.0 / (1.0 + avgper)) * ((high - low) - nz(aatr[1]))  
    aatr
    
adaptiveDeviation(float price, int per)=>
    float m_per = (per > 1) ? per : 1
    float m_fastEnd = math.max(m_per / 2.0, 1)
    float m_slowEnd = m_per * 5
    float m_perDiff = m_slowEnd - m_fastEnd
    
    float difference = math.abs(ta.change(price))
    float signal = 0.
    float noise = 0.
    
    if bar_index > m_per 
        signal := math.abs(price - nz(price[m_per]))
        noise := nz(noise[1]) + difference - nz(difference[m_per])
    else
        for k = 0 to m_per - 1
            noise += nz(difference[k])
    
    float averageper = (signal /noise) * m_perDiff + m_fastEnd
    float alpha = 2.0 / (1.0 + averageper)
    float ema0 = 0., float ema1 = 0.
    ema0 := nz(ema0[1]) + alpha * (price - nz(ema0[1]))
    ema1 := nz(ema1[1])  + alpha * (price * price - nz(ema1[1]))
    float out = math.sqrt(averageper * (ema1 - ema0 * ema0) / math.max(averageper - 1, 1))
    out
    
fmedian(float src, int per)=>
    int midlea = 0
    int midleb = 0
    float[] sortMed = array.new<float>(per, 0.)
    if (per % 2) == 0
        midlea := int((per / 2) - 1)
        midleb := int(per / 2)
    else  
        midlea := int((per - 1) / 2)
        midleb := int(midlea)
    for k = 0 to per - 1
        array.set(sortMed, k, nz(src[k]))
    array.sort(sortMed)
    out = (array.get(sortMed, midlea) + array.get(sortMed, midleb)) / 2
    out
    
filt(float src, simple int len, float filter, filtper, type)=>
    float price = src
    float atr = ta.atr(len)
    float std = ta.stdev(src, len)
    float addev = adaptiveDeviation(src, len)
    float aatr = adaptvieATR(src, len)
    float out = fmedian(src, len)
    float mad = fmedian(math.abs(src - out), filtper)
    float filtdev = filter * (type == "ATR" ? atr: type == "Standard Deviation" ? std : type == "Median Absolute Deviation" ? mad : type == "Adaptive Deviation" ? addev : aatr)
    price := math.abs(price - nz(price[1])) < filtdev ? nz(price[1]) : price
    price
    
banVal(float src, simple int len, float filter, filtper, type)=>
    float atr = ta.atr(len)
    float std = ta.stdev(src, len)
    float addev = adaptiveDeviation(src, len)
    float aatr = adaptvieATR(src, len)
    float out = fmedian(src, len)
    float mad = fmedian(math.abs(src - out), filtper)
    float regdev = ta.dev(src, len)
    float filtdev = filter * (type == "ATR" ? atr : type == "Standard Deviation" ? std : 
         type == "Median Absolute Deviation" ? mad : type == "Adaptive Deviation" ? addev : type == "ER-Adaptive ATR" ? aatr : regdev)
    filtdev
    
smthtype = input.string("Kaufman", "Heiken-Ashi Better Smoothing", options = ["AMA", "T3", "Kaufman"], group=  "Source Settings")
srcoption = input.string("Close", "Source", group= "Source Settings", 
     options = 
     ["Close", "Open", "High", "Low", "Median", "Typical", "Weighted", "Average", "Average Median Body", "Trend Biased", "Trend Biased (Extreme)", 
     "HA Close", "HA Open", "HA High", "HA Low", "HA Median", "HA Typical", "HA Weighted", "HA Average", "HA Average Median Body", "HA Trend Biased", "HA Trend Biased (Extreme)",
     "HAB Close", "HAB Open", "HAB High", "HAB Low", "HAB Median", "HAB Typical", "HAB Weighted", "HAB Average", "HAB Average Median Body", "HAB Trend Biased", "HAB Trend Biased (Extreme)"])

per = input.int(14, "Period", group = "Basic Settings")
nemadpth = input.int(3, "Depth", maxval = 50, minval = 1, group = "Basic Settings")
nematype = input.string("EMA", "Moving Average Type", options = ["EMA", "RMA"] , group = "Basic Settings")

ptype = input.string("ATR", "Price Filter Type", options = ["ATR", "Standard Deviation", "Median Absolute Deviation", "Adaptive Deviation", "ER-Adaptive ATR", "Mean Absolute Deviation"], group=  "Filter Settings")
matype = input.string("ATR", "Moving Average Filter Type", options = ["ATR", "Standard Deviation", "Median Absolute Deviation", "Adaptive Deviation", "ER-Adaptive ATR", "Mean Absolute Deviation"], group=  "Filter Settings")
filterop = input.string("Both", "Filter Options", options = ["Price", "Moving Average Filter", "Both", "None"], group=  "Filter Settings")
filter = input.float(1, "Filter Multiplier", minval = 0, group= "Filter Settings")
filterperiod = input.int(10, "Filter Period", minval = 0, group= "Filter Settings")
madper = input.int(10, "MAD Internal Filter Period", minval = 0, group= "Filter Settings", tooltip = "Median Absolute Deviation only")

bndtype = input.string("ATR", "Band Type Type", options = ["ATR", "Standard Deviation", "Median Absolute Deviation", "Adaptive Deviation", "ER-Adaptive ATR", "Mean Absolute Deviation"], group=  "Bands Settings")
bndmult = input.float(1, "Band Multiplier", minval = 0, group= "Bands Settings")

colorbars = input.bool(true, "Color bars?", group = "UI Options")
showSigs = input.bool(true, "Show signals?", group= "UI Options")
showBands = input.bool(true, "Show bands?", group= "UI Options")

kfl=input.float(0.666, title="* Kaufman's Adaptive MA (KAMA) Only - Fast End", group = "Moving Average Inputs")
ksl=input.float(0.0645, title="* Kaufman's Adaptive MA (KAMA) Only - Slow End", group = "Moving Average Inputs")
amafl = input.int(2, title="* Adaptive Moving Average (AMA) Only - Fast", group = "Moving Average Inputs")
amasl = input.int(30, title="* Adaptive Moving Average (AMA) Only - Slow", group = "Moving Average Inputs")

haclose = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, close)
haopen = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, open)
hahigh = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, high)
halow = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, low)
hamedian = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, hl2)
hatypical = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, hlc3)
haweighted = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, hlcc4)
haaverage = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, ohlc4)

float src = switch srcoption
	"Close" => loxxexpandedsourcetypes.rclose()
	"Open" => loxxexpandedsourcetypes.ropen()
	"High" => loxxexpandedsourcetypes.rhigh()
	"Low" => loxxexpandedsourcetypes.rlow()
	"Median" => loxxexpandedsourcetypes.rmedian()
	"Typical" => loxxexpandedsourcetypes.rtypical()
	"Weighted" => loxxexpandedsourcetypes.rweighted()
	"Average" => loxxexpandedsourcetypes.raverage()
    "Average Median Body" => loxxexpandedsourcetypes.ravemedbody()
	"Trend Biased" => loxxexpandedsourcetypes.rtrendb()
	"Trend Biased (Extreme)" => loxxexpandedsourcetypes.rtrendbext()
	"HA Close" => loxxexpandedsourcetypes.haclose(haclose)
	"HA Open" => loxxexpandedsourcetypes.haopen(haopen)
	"HA High" => loxxexpandedsourcetypes.hahigh(hahigh)
	"HA Low" => loxxexpandedsourcetypes.halow(halow)
	"HA Median" => loxxexpandedsourcetypes.hamedian(hamedian)
	"HA Typical" => loxxexpandedsourcetypes.hatypical(hatypical)
	"HA Weighted" => loxxexpandedsourcetypes.haweighted(haweighted)
	"HA Average" => loxxexpandedsourcetypes.haaverage(haaverage)
    "HA Average Median Body" => loxxexpandedsourcetypes.haavemedbody(haclose, haopen)
	"HA Trend Biased" => loxxexpandedsourcetypes.hatrendb(haclose, haopen, hahigh, halow)
	"HA Trend Biased (Extreme)" => loxxexpandedsourcetypes.hatrendbext(haclose, haopen, hahigh, halow)
	"HAB Close" => loxxexpandedsourcetypes.habclose(smthtype, amafl, amasl, kfl, ksl)
	"HAB Open" => loxxexpandedsourcetypes.habopen(smthtype, amafl, amasl, kfl, ksl)
	"HAB High" => loxxexpandedsourcetypes.habhigh(smthtype, amafl, amasl, kfl, ksl)
	"HAB Low" => loxxexpandedsourcetypes.hablow(smthtype, amafl, amasl, kfl, ksl)
	"HAB Median" => loxxexpandedsourcetypes.habmedian(smthtype, amafl, amasl, kfl, ksl)
	"HAB Typical" => loxxexpandedsourcetypes.habtypical(smthtype, amafl, amasl, kfl, ksl)
	"HAB Weighted" => loxxexpandedsourcetypes.habweighted(smthtype, amafl, amasl, kfl, ksl)
	"HAB Average" => loxxexpandedsourcetypes.habaverage(smthtype, amafl, amasl, kfl, ksl)
    "HAB Average Median Body" => loxxexpandedsourcetypes.habavemedbody(smthtype, amafl, amasl, kfl, ksl)
	"HAB Trend Biased" => loxxexpandedsourcetypes.habtrendb(smthtype, amafl, amasl, kfl, ksl)
	"HAB Trend Biased (Extreme)" => loxxexpandedsourcetypes.habtrendbext(smthtype, amafl, amasl, kfl, ksl)
	=> haclose
	
src := filterop == "Both" or filterop == "Price" and filter > 0 ? filt(src, filterperiod, filter, madper, ptype) : src

out = nema(nematype, src, per, nemadpth, 50)

out := filterop == "Both" or filterop == "Moving Average Filter" and filter > 0 ? filt(out, filterperiod, filter, madper, matype)  : out
sig = nz(out[1])

state = out > sig ? 1 : out < sig ? -1 : 0
pregoLong = out > sig and (nz(out[1]) < nz(sig[1]) or nz(out[1]) == nz(sig[1]))
pregoShort = out < sig and (nz(out[1]) > nz(sig[1]) or nz(out[1]) == nz(sig[1]))

contsw = 0
contsw := nz(contsw[1])
contsw := pregoLong ? 1 : pregoShort ? -1 : nz(contsw[1])

goLong = pregoLong and nz(contsw[1]) == -1
goShort = pregoShort and nz(contsw[1]) == 1 

bandout = banVal(src, filterperiod, bndmult, madper, bndtype)
bndup = out + bandout
bnddn = out - bandout

plot(showBands ? bndup : na, "Upper Band", color = color.gray, linewidth = 1)
plot(showBands ? bnddn : na, "Lower Band", color = color.gray, linewidth = 1)

var color colorout = na
colorout := state == -1 ? redcolor : state == 1 ? greencolor : nz(colorout[1])

plot(out, "Step NTMA", color = colorout, linewidth = 2)
barcolor(colorbars ? colorout : na)

plotshape(showSigs and goLong, title = "Long", color = color.yellow, textcolor = color.yellow, text = "L", style = shape.triangleup, location = location.belowbar, size = size.tiny)
plotshape(showSigs and goShort, title = "Short", color = color.fuchsia, textcolor = color.fuchsia, text = "S", style = shape.triangledown, location = location.abovebar, size = size.tiny)

alertcondition(goLong, title = "Long", message = "Variety N-Tuple Moving Averages w/ Variety Stepping [Loxx]: Long\nSymbol: {{ticker}}\nPrice: {{close}}")
alertcondition(goShort, title = "Short", message = "Variety N-Tuple Moving Averages w/ Variety Stepping [Loxx]: Short\nSymbol: {{ticker}}\nPrice: {{close}}")

