Filtered, N-Order Power-of-Cosine, Sinc 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("Filtered, N-Order Power-of-Cosine, Sinc FIR Filter [Loxx]",
     shorttitle = "FNOPOCSFIRF [Loxx]",
     overlay = true, 
     timeframe="", 
     timeframe_gaps = true)

import loxx/loxxexpandedsourcetypes/4

greencolor = #2DD204
redcolor = #D2042D 

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

duelElementLagReducer(float[] coeff, int LagReductionFactor)=>
    if LagReductionFactor > 0
        per = array.size(coeff)
        for i = per - 1 to 0
            if i >= LagReductionFactor
                array.set(coeff, i, 2 * array.get(coeff, i) - array.get(coeff, i - LagReductionFactor))
            else
                array.set(coeff, i, 2 * array.get(coeff, i))
    coeff
    
nOrderPowerOfCosineSinc(int per, int order, float frequencyCutoff, int multiplier)=>
    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)

    for k = 0 to order 
        array.set(pastri, k, nz(fact(order) / (fact(order - k) * fact(k)), 1))

    array<float> outpastri = array.slice(pastri, 0, order / 2)
    array.reverse(outpastri)
    float[] coeffs = array.new<float>(per, 0)
    int N = per - 1
    float sum = 0

    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)
        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 * n / N)
            sign *= -1
        coeff := coeff / (array.sum(outpastri) + ppastri)
        array.set(coeffs, n, array.get(coeffs, n) * coeff)   
        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
    
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(4, "Alpha", minval = 2, step = 2, maxval = 50, group = "Basic Settings")
sth = input.float(0.1, "Clutter Filter Threshold", group = "Basic Settings", step = 0.001)
lagr = input.int(0, "Lag Reduction Factor", group = "Basic Settings")

mult = input.int(1, "Multiplier", group = "Sinc Settings")
fcut = input.float(0.01, "Frequency Cutoff", maxval = 0.5, minval = 0, step = 0.01, group = "Sinc Settings")

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 = nOrderPowerOfCosineSinc(per, type, fcut, mult)
duelElementLagReducer(coeffs, lagr)

coeffsSum = array.sum(coeffs)

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