STD-Filtered, Variety FIR Digital Filters w/ ATR Bands [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("STD-Filtered, Variety FIR Digital Filters w/ ATR Bands [Loxx]",
     shorttitle = "STDFVFIRDFB [Loxx]", 
     overlay = true, 
     timeframe="", 
     timeframe_gaps = true)

import loxx/loxxexpandedsourcetypes/4

greencolor = #2DD204
redcolor = #D2042D 

darkGreenColor =  #1B7E02 
darkRedColor = #93021F

fir_hamm = "Hamming"
fir_hanning = "Hanning"
fir_black = "Blackman"
fir_blackh = "Blackman Harris"
fir_blacknutt = "Blackman Nutall"
fir_nutt = "Nutall"
fir_bartzep = "Bartlet Zero End Points"
fir_barthann = "Bartlet-Hann"
fir_hann = "Hann"
fir_sine = "Sine"
fir_lan = "Lanczos"
fir_flat = "Flat Top"
    
ema(float src, float per)=>
    float alpha = 2.0 / (1.0 + per)
    float out = src
    out := nz(out[1]) + alpha * (src - nz(out[1]))
    out 

sma(float src, float per)=>
    float sum = 0
    float out = src
    for k = 0 to per - 1
        sum += nz(src[k])
    out := sum / per 
    out

smma(float src, float per)=>
    float alpha = 1.0 / per
    float out = src
    out := nz(out[1]) + alpha * (src - nz(out[1]))
    out 
    
lwma(float src, float per)=>
    float sumw = 0
    float sum = 0
    float out = 0
    for i = 0 to per - 1
        float weight = (per - i) * per
        sumw += weight
        sum  += nz(src[i]) * weight
    out := sum / sumw
    out
    
pwma(float src, float per, float pow)=>
    float sum = 0
    float sumw = 0
    float out = 0
    for k = 0 to per - 1 
        float weight = math.pow((per - k), pow)
        sumw += weight
        sum  += nz(src[k]) * weight
    out := sum / sumw
    out
    
lsma(float src, float per)=>
    float out = 0
    out :=  3.0 * lwma(src, per) - 2.0 * sma(src, per)
    out 
    
hma(float src, float per)=>
    int HalfPeriod = math.floor(per / 2)
    int HullPeriod = math.floor(math.sqrt(per))
    float out = 0
    float price1 = 2.0 * lwma(src, HalfPeriod) - lwma(src, per)
    out := lwma(price1, HullPeriod)
    out
    
tma(float src, float per)=>
    float half = (per + 1.0) / 2.0
    float sum  = 0
    float sumw = 0
    float out = 0
    for k = 0 to per - 1 
        float weight = k + 1
        if weight > half
            weight := per - k
        sumw += weight
        sum  += weight * nz(src[k]) 
    out := sum / sumw
    out
    
swma(float src, float per)=>
    float sum = 0
    float sumw = 0
    float out = 0
    for k = 0 to per - 1  
        weight = math.sin((k + 1) * math.pi / (per + 1))
        sumw += weight
        sum  += weight * nz(src[k])
    out := sum / sumw
    out
    
vwma(float src, float per)=>
    float sum  = 0
    float sumw = 0
    float out = 0
    for k = 0 to per - 1
        float weight = nz(volume[k])
        sumw += weight
        sum  += weight * nz(src[k])
    out := sum / sumw
    out

nonlagma(float src, float len)=>
    float cycle = 4.0
    float coeff = 3.0 * math.pi
    float phase = len - 1.0
    int _len = int(len * cycle + phase)
    float weight = 0.
    float alfa = 0.
    float out = 0.
    float[] alphas = array.new_float(_len, 0)
    for k = 0 to _len - 1
        float t = 0.
        t := k <= phase - 1 ?  1.0 * k / (phase - 1) : 1.0 + (k - phase + 1) * (2.0 * cycle - 1.0) / (cycle * len -1.0)
        beta = math.cos(math.pi * t)
        float g = 1.0/(coeff * t + 1)
        g := t <= 0.5  ? 1 : g
        array.set(alphas, k, g * beta)
        weight += array.get(alphas, k)
    if (weight > 0)  
        float sum = 0.
        for k = 0 to _len - 1
            sum += array.get(alphas, k) * nz(src[k])
        out := (sum / weight)
    out
    
powerOfCosineDesign(string type, int per, float frequencyCutoff, int multiplier)=>
    array<float> coeffs = array.new<float>(per, 0)
    float sum = 0
    int N = per - 1
    
    for n = 0 to per - 1
    
        float div = n - N / 2.0
        
        if div == 0
            array.set(coeffs, n, 2.0 * math.pi * frequencyCutoff)
        else        
            array.set(coeffs, n, math.sin(2.0 * math.pi * frequencyCutoff * div) / div)
            
        float temp = 0.
        switch type
            fir_hamm =>
                temp := 0.54 - 0.46 * math.cos(2 * math.pi * n / N)
                
            fir_hanning =>
                temp := 0.50 - 0.50 * math.cos(2 * math.pi * n / N)
                
            fir_black =>
                temp := 0.42 
                     - 0.50 * math.cos(2 * math.pi * n / N) 
                     + 0.08 * math.cos(4 * math.pi * n / N)
                     
            fir_blackh =>
                temp := 0.35875 
                     - 0.48829 * math.cos(2 * math.pi * n / N) 
                     + 0.14128 * math.cos(4 * math.pi * n / N) 
                     + 0.01168 * math.cos(6 * math.pi * n / N)
                     
            fir_blacknutt =>
                temp := 0.3635819
                     - 0.4891775 * math.cos(2 * math.pi * n / N)
                     + 0.1365995 * math.cos(4 * math.pi * n / N)
                     + 0.0106411 * math.cos(6 * math.pi * n / N)
                     
            fir_nutt =>
                temp := 0.355768 
                     - 0.487396 * math.cos(2 * math.pi * n / N)
                     + 0.144232 * math.cos(4 * math.pi * n / N)
                     + 0.012604 * math.cos(6 * math.pi * n / N)
                 
            fir_bartzep =>
                temp := 2.0 / N * (N / 2.0 - math.abs(n - N / 2))
                
            fir_barthann =>
                temp := 0.62 - 0.48 * math.abs(n / N - 0.5) * 0.38 * math.cos(2.0 * math.pi * n / N)
                
            fir_hann =>
                temp := 0.50 * (1.0 - math.cos(2.0 * math.pi * n / N))
                
            fir_sine =>
                temp := math.sin(math.pi * n / N)
                
            fir_lan =>
                float ttx = (n / N) - 1.0
                if ttx == 0
                    temp := 0
                else
                    temp := math.sin(ttx) / (ttx)
                    
            fir_flat =>
                temp := 1
                     - 1.93 * math.cos(2 * math.pi * n / N)
                     + 1.29 * math.cos(4 * math.pi * n / N)
                     + 0.388 * math.cos(6 * math.pi * n / N)
                     + 0.388 * math.cos(8 * math.pi * n / N)

        array.set(coeffs, n, array.get(coeffs, n) * temp)   
        sum += array.get(coeffs, n)
        
    for k = 0 to per - 1
        array.set(coeffs, k, array.get(coeffs, k) / sum)
        array.set(coeffs, k, array.get(coeffs, k) * multiplier)
    coeffs
    
stdFilter(float src, int len, float filter)=>
    float price = src
    float filtdev = filter * ta.stdev(src, len)
    price := math.abs(price - nz(price[1])) < filtdev ? nz(price[1]) : price
    price

smthtype = input.string("Kaufman", "Heiken-Ashi Better Smoothing", options = ["AMA", "T3", "Kaufman"], group=  "Source Settings")
srcoption = input.string("HAB Median", "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(30, "Period", step = 2, group = "Basic Settings")
type = input.string(fir_hamm, "Filter Type", options = [fir_hamm, fir_hanning, fir_black, fir_blackh, fir_blacknutt, fir_nutt, fir_bartzep, fir_barthann, fir_hann, fir_sine, fir_lan, fir_flat], group = "Basic Settings")
mult = input.int(1, "Multiplier", group = "Basic Settings")
fcut = input.float(0.01, "Frequency Cutoff", maxval = 0.5, minval = 0, step = 0.01, group = "Basic Settings")
causal = input.bool(true, "Make it causal? ", group = "Basic Settings")

rngper = input.int(14, "Range Period", group = "Bands Settings")
rngtype = input.string("Simple Moving Average - SMA", "Double Smoothing MA Type", options = ["Exponential Moving Average - EMA"
     , "Hull Moving Average - HMA"
     , "Linear Regression Value - LSMA (Least Squares Moving Average)"
     , "Linear Weighted Moving Average - LWMA"
     , "Non-Lag Moving Average"
     , "Parabolic Weighted Moving Average"
     , "Simple Moving Average - SMA"
     , "Sine Weighted Moving Average"
     , "Smoothed Moving Average - SMMA"
     , "Triangular Moving Average - TMA"
     , "Volume Weighted Moving Average - VWMA"],
     group = "Bands Settings")
     
UpDeviation = input.float(2, "Up Deviation", group = "Bands Settings")
DnDeviation = input.float(2, "Down Deviation", group = "Bands Settings")

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

filterop = input.string("Both", "Filter Options", options = ["Price", "STDFVFIRDFB", "Both", "None"], group=  "Filter Settings")
filter = input.float(0, "Filter Devaitions", minval = 0, group= "Filter Settings")
filterperiod = input.int(15, "Filter Period", minval = 0, group= "Filter Settings")

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
	
variant(type, src, len) =>
    out = 0.0
    switch type
        "Exponential Moving Average - EMA" => out := ema(src, len)
        "Hull Moving Average - HMA" => out := hma(src, len)
        "Linear Regression Value - LSMA (Least Squares Moving Average)" => out := lsma(src, len)
        "Linear Weighted Moving Average - LWMA" => out := lwma(src, len)
        "Non-Lag Moving Average" => out := nonlagma(src, len)
        "Parabolic Weighted Moving Average" => out := pwma(src, len, 2)
        "Simple Moving Average - SMA" => out := sma(src, len)
        "Sine Weighted Moving Average" => out := swma(src, len)
        "Smoothed Moving Average - SMMA" => out := smma(src, len)
        "Triangular Moving Average - TMA" => out := tma(src, len)
        "Volume Weighted Moving Average - VWMA" => out := vwma(src, len)
    out
    
src := filterop == "Both" or filterop == "Price" and filter > 0 ? stdFilter(src, filterperiod, filter) : src

coeffs = powerOfCosineDesign(type, per, fcut, mult)

half = math.floor(per/2.0)
out = 0.
sum = 0.
for k = 0 to per - 1
    if causal
        out += nz(src[k]) * array.get(coeffs, k)
    else 
        if (k - half) >= 0 and (k - half) < bar_index
            out += nz(src[k - half]) * array.get(coeffs, k)
            sum += array.get(coeffs, k)
            
if sum != 0 
    out /= sum

out := filterop == "Both" or filterop == "STDFVFIRDFB" and filter > 0 ? stdFilter(out, filterperiod, filter) : out
sig = nz(out[1])

float rng = variant(rngtype, ta.tr, rngper)
upb = out + UpDeviation * rng
dhb = out - DnDeviation * rng

state = 0
if causal
    if low < nz(low[1]) and nz(high[1]) > nz(upb[1]) and nz(close[1]) > nz(open[1]) and close < open
        state := 1
    if high > nz(high[1]) and nz(low[1]) < nz(dhb[1]) and nz(close[1]) < nz(open[1]) and close > open
        state := -1
else 
    state := out > sig ? 1 : -1
    
pregoLong = state == 1
pregoShort = state == -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 

color colorout = na
colorout := state == 1 ? greencolor : state == -1 ? redcolor : colorout[1]

plot(out, "STDFVFIRDFB", color = colorout, linewidth = 3)
plot(upb, "Up band", color = darkGreenColor)
plot(dhb, "Down band", color = darkRedColor)

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 = "STD-Filtered, Variety FIR Digital Filters w/ ATR Bands [Loxx]: Long\nSymbol: {{ticker}}\nPrice: {{close}}")
alertcondition(goShort, title = "Short", message = "STD-Filtered, Variety FIR Digital Filters w/ ATR Bands [Loxx]: Short\nSymbol: {{ticker}}\nPrice: {{close}}")


