STD/C-Filtered, N-Order Power-of-Cosine FIR Filter [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/C-Filtered, N-Order Power-of-Cosine FIR Filter [Loxx]",
     shorttitle = "STDCFNOPOCFIRF [Loxx]",
     overlay = true, max_lines_count = 500)

import loxx/loxxexpandedsourcetypes/4

greencolor = #2DD204
redcolor = #D2042D 

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

//-----------------------------------------------/
// Binomial Expansions Using Pascal’s Triangle of 
// Power-of-CosineFamily of FIR Digtial Filters 
//-----------------------------------------------/
powerOfCosineDesign(int per, int order)=>
    // Create alpha0 which equals the prevoius Pascal Traingle 
    // order's maximum of the left side; that is, the middle 
    // value of pervious order of the Pascal's triangle 
    float ppastri = 0 
    var float[] pastri = array.new<float>(order + 1, 0.)
    int pdepth = order - 1
    for k = 0 to order / 2 - 1
        ppastri := nz(fact(pdepth) / (fact(pdepth - k) * fact(k)), 1)
        
    // Get all values from Pascal's triangle from row = order
    // maximum of the left side, that is, the middle 
    // value of Pascal's triangle 
    for k = 0 to order 
        array.set(pastri, k, nz(fact(order) / (fact(order - k) * fact(k)), 1))
        
    // We are only worried about the left side of Pascal values
    // slice array to get those values
    array<float> outpastri = array.slice(pastri, 0, order / 2)
    
    // Invert array to order numers correctly for kernel calculation
    array.reverse(outpastri)
    float[] coeffs = array.new<float>(per, 0)
    int N = per + 1
    for i = 0 to per - 1 
        int ni = i + 1
        float X = ni / N
        int sign = -1
        float coeff = ppastri
        for k = 0 to array.size(outpastri) - 1
            coeff += sign * array.get(outpastri, k) * math.cos((k + 1) * 2 * math.pi * X)
            sign *= -1
        coeff := coeff / (array.sum(outpastri) + ppastri)
        array.set(coeffs, i, coeff)
    [coeffs, array.sum(coeffs)]

clutterFilt(float src, float threshold)=>
    bool out = math.abs(ta.roc(src, 1)) > threshold
    out
    
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(14, "Period", group = "Basic Settings")
type = input.int(2, "Alpha", minval = 2, step = 2, maxval = 50, group = "Basic Settings")
sth = input.float(0, "Clutter Filter Threshold", group = "Basic Settings", step = 0.001)

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

filterop = input.string("Both", "Filter Options", options = ["Price", "STDCFNOPOCFIRF", "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

src := filterop == "Both" or filterop == "Price" and filter > 0 ? stdFilter(src, filterperiod, filter) : src

[coeffs, coeffsSum] = powerOfCosineDesign(per, type)

float dSum = 0
for k = 0 to array.size(coeffs) - 1
    dSum += nz(src[k]) * array.get(coeffs, k)

out = coeffsSum != 0 ? dSum / coeffsSum : 0

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

filtTrend = clutterFilt(out, sth)
state = filtTrend ? (out > sig ? 1 : out < sig ? -1 : 0) : 0

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 := filtTrend ? (state == 1 ? greencolor : state == -1 ? redcolor : showdeadzones ? color.gray : colorout[1]) : showdeadzones ? color.gray : colorout[1]

plot(out, "STDCFNOPOCFIRF", color = colorout, linewidth = 3)

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/C-Filtered, N-Order Power-of-Cosine FIR Filter [Loxx]: Long\nSymbol: {{ticker}}\nPrice: {{close}}")
alertcondition(goShort, title = "Short", message = "STD/C-Filtered, N-Order Power-of-Cosine FIR Filter [Loxx]: Short\nSymbol: {{ticker}}\nPrice: {{close}}")
