Variety RSI of Fast Discrete Cosine Transform [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 RSI of Fast Discrete Cosine Transform [Loxx]", 
     shorttitle='VRSIFDCT [Loxx]', 
     overlay = false, 
     timeframe="", 
     timeframe_gaps = true)
     
import loxx/loxxexpandedsourcetypes/4
import loxx/loxxvarietyrsi/1
greencolor = #2DD204
redcolor = #D2042D 

SM02 = 'Slope'
SM03 = 'Middle Crosses'

//==========================================================================================
//Fast Discrete Cosine Transform

//The algorithm performs a fast cosine transform of the real
//function defined by nn samples on the real axis.

//Depending on the passed parameters, it can be executed
//both direct and inverse conversion.

//Input parameters:
//    tnn - Number of function values minus one. Should be 1024
//            degree of two. The algorithm does not check
//            correct value passed.
//    a - array [0 .. nn] of Real 1025
//            Function values.
//    InverseFCT
//        - the direction of the transformation.
//            True if reverse, False if direct.
//            
//Output parameters:
//    a - the result of the transformation. For more details, see
//            description on the site. https://www.alglib.net/fasttransforms/fft.php
//==========================================================================================

_fastcosinetransform(float[] a, int tnn, bool inversefct)=>
    int j = 0
    int n2 = 0
    float sum = 0.
    float y1 = 0.
    float y2 = 0.
    float theta = 0.
    float wi = 0.
    float wpi = 0.
    float wr = 0.
    float wpr = 0.
    float wtemp = 0.
    float twr = 0.
    float twi = 0.
    float twpr = 0.
    float twpi = 0.
    float twtemp = 0.
    float ttheta = 0.
    int i = 0
    int i1 = 0
    int i2 = 0
    int i3 = 0
    int i4 = 0
    float c1 = 0.
    float c2 = 0.
    float h1r = 0.
    float h1i = 0.
    float h2r = 0.
    float h2i = 0.
    float wrs = 0.
    float wis = 0.
    int nn = 0
    int n = 0
    int mmax = 0
    int m = 0
    int istep = 0
    int isign = 0
    float tempr = 0.
    float tempi = 0.

    while true
        if (tnn == 1) 
            y1 := array.get(a, 0)
            y2 := array.get(a, 1)
            array.set(a, 0, 0.5 * (y1 + y2))
            array.set(a, 1, 0.5 * (y1 - y2))
            if inversefct
                array.set(a, 0, array.get(a, 0) * 2)
                array.set(a, 1, array.get(a, 1) * 2)
                break
        wi := 0
        wr := 1
        theta := math.pi / tnn
        wtemp := math.sin(theta * 0.5)
        wpr := -2.0 * wtemp * wtemp
        wpi := math.sin(theta)
        sum := 0.5 * (array.get(a, 0) - array.get(a, tnn))
        array.set(a, 0, 0.5 * (array.get(a, 0) + array.get(a, tnn)))
        n2 := tnn + 2
        for jx = 2 to tnn / 2  
            wtemp := wr
            wr := wtemp * wpr - wi * wpi + wtemp
            wi := wi * wpr + wtemp * wpi + wi
            y1 := 0.5 * (array.get(a, jx - 1) + array.get(a, n2 - jx - 1))
            y2 := array.get(a, jx - 1) - array.get(a, n2 - jx - 1)
            array.set(a, jx - 1, y1 - wi * y2)
            array.set(a, n2 - jx - 1, y1 + wi * y2)
            sum := sum + wr * y2
        
        ttheta := 2.0 * math.pi / tnn
        c1 := 0.5
        c2 := -0.5
        isign := 1
        n := tnn
        nn := tnn / 2
        j := 1
        for ii = 1 to nn  
            i := 2 * ii - 1
            if (j > i) 
                tempr := array.get(a, j - 1)
                tempi := array.get(a, j)
                array.set(a, j - 1, array.get(a, i - 1))
                array.set(a, j, array.get(a, i))
                array.set(a, i - 1, tempr)
                array.set(a, i, tempi)
            m := n / 2
            while (m >= 2 and j > m) 
                j := j - m
                m := m / 2
            j := j + m
        mmax := 2
        while (n > mmax) 
            istep := 2 * mmax
            theta := 2.0 * math.pi / (isign * mmax)
            wpr := -2.0 * math.pow(math.sin(0.5 * theta), 2)
            wpi := math.sin(theta)
            wr := 1.0
            wi := 0.0
            for ii = 1 to mmax / 2  
                m := 2 * ii - 1
                for jj = 0 to (n - m) / istep  
                    i := m + jj * istep
                    j := i + mmax
                    tempr := wr * array.get(a, j - 1) - wi * array.get(a, j)
                    tempi := wr * array.get(a, j) + wi * array.get(a, j - 1)
                    array.set(a, j - 1, array.get(a, i - 1) - tempr)
                    array.set(a, j, array.get(a, i) - tempi)
                    array.set(a, i - 1, array.get(a, i - 1) + tempr)
                    array.set(a, i, array.get(a, i) + tempi)
                
                wtemp := wr
                wr := wr * wpr - wi * wpi + wr
                wi := wi * wpr + wtemp * wpi + wi
            
            mmax := istep
        twpr := -2.0 * math.pow(math.sin(0.5 * ttheta), 2)
        twpi := math.sin(ttheta)
        twr := 1.0 + twpr
        twi := twpi
        for ix = 2 to tnn / 4 + 1  
            i1 := ix + ix - 2
            i2 := i1 + 1
            i3 := tnn + 1 - i2
            i4 := i3 + 1
            wrs := twr
            wis := twi
            h1r := c1 * (array.get(a, i1) + array.get(a, i3))
            h1i := c1 * (array.get(a, i2) - array.get(a, i4))
            h2r := -c2 * (array.get(a, i2) + array.get(a, i4))
            h2i := c2 * (array.get(a, i1) - array.get(a, i3))
            array.set(a, i1, h1r + wrs * h2r - wis * h2i)
            array.set(a, i2, h1i + wrs * h2i + wis * h2r)
            array.set(a, i3, h1r - wrs * h2r + wis * h2i)
            array.set(a, i4, -h1i + wrs * h2i + wis * h2r)
            twtemp := twr
            twr := twr * twpr - twi * twpi + twr
            twi := twi * twpr + twtemp * twpi + twi
        
        h1r := array.get(a, 0)
        array.set(a, 0, h1r + array.get(a, 1))
        array.set(a, 1, h1r - array.get(a, 1))
        array.set(a, tnn, array.get(a, 1))
        array.set(a, 1, sum)
        j := 4
        while (j <= tnn) 
            sum := sum + array.get(a, j - 1)
            array.set(a, j - 1, sum)
            j := j + 2
        if (inversefct)
            for jx = 0 to tnn 
                array.set(a, jx, array.get(a, jx) * 2 / tnn)
        break
    a
 
smthtype = input.string("Kaufman", "Heiken-Ashi Better Smoothing", options = ["AMA", "T3", "Kaufman"], group= "Source Settings")
srcin = 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)"])

//N specifies series length (power of two);
//SS - A smoothening coefficient in the resulting spectrum zeroes out frequency ratios exceeding set value. SS cannot exceed 2^N. Close series fully repeats itself if SS = 2^N.
windowper = input.string("256", "Window Period", options = ["16", "32", "64", "128", "256", "512", "1024", "2048"], group = "Basic Settings")
smthcutoff = input.int(12, "Smoothing Period", group = "Basic Settings")
arraylevl = input.int(0, "Output Level", group = "Basic Settings", minval = 0, 
     tooltip = "Experimental, default and utility is the 0 index default. Adjusting this value upward smooths the signal. This value is the index of the of the Inverse Fast Cosine Transform output array.")

rsiper = input.int(15, "RSI Period", group= "RSI Settings")
rsitype = input.string("Regular", "RSI Type", options = ["RSX", "Regular", "Slow", "Rapid", "Harris", "Cuttler", "Ehlers Smoothed"], group = "RSI Settings")

sigtype = input.string(SM03, "Signal type", options = [SM02, SM03], group = "Signal Settings")

colorbars = input.bool(true, "Color bars?", group= "UI Options")
showsignals = input.bool(true, "Show signals?", 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)

src = switch srcin
	"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

rsimode = switch rsitype
	"RSX" => "rsi_rsx"
	"Regular" => "rsi_rsi"
	"Slow" => "rsi_slo"
	"Rapid" => "rsi_rap"
	"Harris" => "rsi_har"
	"Cuttler" => "rsi_cut"
	"Ehlers Smoothed" => "rsi_ehl"
	=> "rsi_rsi"

Window = str.tonumber(windowper) 

//check that indow is power of 2
n = int(math.log(Window) / math.log(2))
N = int(math.max(math.pow(2, n), 16))

var aa = array.new<float>(N + 1, 0.)

//fill caculation array with source values
for i = 0 to N - 1
    array.set(aa, i, nz(src[i])) 

M = array.size(aa)
end = M - 1
smthcutoff := int(math.min(smthcutoff, M))

//regular pass of FCT
_fastcosinetransform(aa, N, false)

//filter of FCT, values above smthcutoff are zeroed out
for k = 0 to end 
    if k >= smthcutoff
        array.set(aa, k, 0.)

//inverse pass of FCT
_fastcosinetransform(aa, N, true)

//we are only interested in the first value; although accessing additional values can create ribbons and boundaries
arraylevlout = math.min(array.size(aa) - 1, arraylevl)

out = array.get(aa, arraylevlout) 

out := loxxvarietyrsi.rsiVariety(rsimode, out, rsiper)
sig = nz(out[1])

mid = 50

state = 0.
if sigtype == SM02
    if (out < sig) 
        state := -1
    if (out > sig) 
        state := 1
else if sigtype == SM03
    if (out < mid) 
        state := -1
    if (out > mid)
        state := 1
        
colorout = state == -1 ? redcolor : state == 1 ? greencolor : color.gray

plot(out, "RSI FDCT", color = colorout, linewidth = 2)
plot(mid, "Middle", color = bar_index % 2 ? color.gray : na)
barcolor(colorout)

goLong = sigtype == SM02 ? ta.crossover(out, sig) : ta.crossover(out, mid) 
goShort = sigtype == SM02 ? ta.crossunder(out, sig) : ta.crossunder(out, mid) 

plotshape(goLong and showsignals, title = "Long", color = color.yellow, textcolor = color.yellow, text = "L", style = shape.triangleup, location = location.bottom, size = size.auto)
plotshape(goShort and showsignals, title = "Short", color =  color.fuchsia, textcolor = color.fuchsia, text = "S", style = shape.triangledown, location = location.top, size = size.auto)

alertcondition(goLong, title="Long", message="Variety RSI of Fast Discrete Cosine Transform [Loxx]: Long\nSymbol: {{ticker}}\nPrice: {{close}}")
alertcondition(goShort, title="Short", message="Variety RSI of Fast Discrete Cosine Transform [Loxx]: Short\nSymbol: {{ticker}}\nPrice: {{close}}")


