Fourier Spectrometer of Price w/ Extrapolation Forecast [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("Fourier Spectrometer of Price w/ Extrapolation Forecast [Loxx]", 
     shorttitle = "FSPEF [Loxx]",
     overlay = false, 
     max_lines_count = 500)

import loxx/loxxexpandedsourcetypes/4

redcolor = #ff1100
orangecolor = #ff5d00
greencolor = #0cb51a
bluecolor = #2157f3
purplecolor = #673ab7
fuchsiacolor = #e91e63
yellowcolor = #ffdd00


specOpsLinearRegression(src, int i0, int i1, float[] aArr)=>
    int aPeriod = 0 
    int rRetError = 0
    float aVal_0 = 0
    float aVal_1 = 0
    float aB = 0
    float aMaxDev = 0
    float aStdError = 0
    float aRSquared = 0
    float x = 0
    float y = 0
    float y1 = 0
    float y2 = 0
    float sumy = 0
    float sumx = 0
    float sumxy = 0
    float sumx2 = 0
    float sumy2 = 0
    float sumx22 = 0
    float sumy22 = 0
    float div1 = 0
    float div2 = 0

    aPeriod := i1 - i0 + 1

    for int i = 0 to aPeriod - 1
        y := nz(src[i0 + i])
        x := i
        sumy += y
        sumxy += y * i
        sumx += x
        sumx2 += math.pow(x, 2)
        sumy2 += math.pow(y, 2)

    sumx22 := math.pow(sumx, 2)
    sumy22 := math.pow(sumy, 2)
    div1 := sumx2 * aPeriod - sumx22
    div2 := math.sqrt((aPeriod * sumx2 - sumx22) * (aPeriod * sumy2 - sumy22))

    //regression line 
    if div1 != 0.0
        aB := (sumxy * aPeriod - sumx * sumy) / div1
        aVal_0 := (sumy - sumx * aB) / aPeriod
        aVal_1 := aVal_0 + aB * (aPeriod - 1)
        rRetError += -1
    else 
        rRetError += -1

    //stderr & maxdev
    aMaxDev := 0
    aStdError := 0

    for i = 0 to aPeriod - 1
        y1 := nz(src[i0 + i])
        y2 := aVal_0 + aB * i
        aMaxDev := math.max(math.abs(y1 - y2), aMaxDev)
        aStdError += math.pow(y1 - y2, 2)
    
    aStdError := math.sqrt(aStdError / aPeriod)

    //rsquared
    if div2 != 0
        aRSquared := math.pow((aPeriod * sumxy - sumx * sumy) / div2, 2)
    else 
        rRetError += -2

    for i = 0 to aPeriod - 1
        y := nz(src[i0 + i])
        x := aVal_0 + i * (aVal_1 - aVal_0) / aPeriod
        array.set(aArr, i, y - x)
    
    rRetError

specOpsArcTan(float aS, float aC)=>
    float out = 0
    while true
        if not aS
            out := 0
            break
        if not aC 
            if aS > 0
                out := (math.atan(1) * 2)
                break
            else if (aS < 0) 
                out := (math.atan(1) * 6)
                break
        else 
            if aS > 0
                if aC > 0
                    out := (math.atan(aS / aC))
                    break
                else 
                    out := (math.atan(aS / aC) + math.atan(1) * 4)
                    break
            else 
                if aC > 0 
                    out := (math.atan(aS / aC) + math.atan(1) * 8)
                    break
                else 
                    out := (math.atan(aS / aC) + math.atan(1) * 4)
                    break
    out

specOpsFourier(float[] aArr)=>
    int tN = array.size(aArr)
    int tM = int(math.max(tN / 2, 1))

    float[] aA = array.new<float>(tN, 0)
    float[] aB = array.new<float>(tN, 0)
    float[] aR = array.new<float>(tN, 0)
    float[] aF = array.new<float>(tN, 0)

    for ti = 1 to tM - 1
        array.set(aA, ti, 0)
        array.set(aB, ti, 0)
        for tj = 0 to tN - 1
            array.set(aA, ti, array.get(aA, ti) + array.get(aArr, tj) * math.sin(ti * 2 * math.pi * tj / tN))
            array.set(aB, ti, array.get(aA, ti) + array.get(aArr, tj) * math.cos(ti * 2 * math.pi * tj / tN))
        
        array.set(aA, ti, 2 * array.get(aA, ti) / tN)
        array.set(aB, ti, 2 * array.get(aB, ti) / tN)
        array.set(aR, ti, math.sqrt(math.pow(array.get(aA, ti), 2) + math.pow(array.get(aB, ti), 2)))
        array.set(aF, ti, specOpsArcTan(array.get(aB, ti), array.get(aA, ti)))

    [aA, aB, aR, aF]

smthtype = input.string("Kaufman", "Heikin-Ashi Better Caculation Type", 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)"])

per = input.int(500, "Period", maxval = 1000, minval = 64, group = "Basic Settings")
startFromBar = input.int(100, "Future Bars", maxval = 100, group = "Basic Settings")

freq1col = input.color(greencolor, "Frequency 1 Color", group = "UI Settings")
freq2col = input.color(redcolor, "Frequency 2 Color", group = "UI Settings")
freq3col = input.color(purplecolor, "Frequency 3 Color", group = "UI Settings")
freq4col = input.color(color.yellow, "Frequency 4 Color", group = "UI Settings")
freq5col = input.color(bluecolor, "Frequency 5 Color", group = "UI Settings")
freq6col = input.color(color.white, "Frequency 6 Color", group = "UI Settings")
freq7col = input.color(fuchsiacolor, "Frequency 7 Color", group = "UI Settings")
freq8col = input.color(color.aqua, "Frequency 8 Color", group = "UI Settings")

freq1width = input.int(4, "Frequency 1 Width", group = "UI Settings")
freq2width = input.int(3, "Frequency 2 Width", group = "UI Settings")
freq3width = input.int(2, "Frequency 3 Width", group = "UI Settings")
freq4width = input.int(1, "Frequency 4 Width", group = "UI Settings")
freq5width = input.int(1, "Frequency 5 Width", group = "UI Settings")
freq6width = input.int(1, "Frequency 6 Width", group = "UI Settings")
freq7width = input.int(1, "Frequency 7 Width", group = "UI Settings")
freq8width = input.int(1, "Frequency 8 Width", group = "UI 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 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.hatrendb(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
	
float[] srcarr = array.new<float>(per + 1, 0)
float[] aArr = array.new<float>(per, 0)

var pvlines1 = array.new_line(0)
var pvlines2 = array.new_line(0)
var pvlines3 = array.new_line(0)
var pvlines4 = array.new_line(0)
var pvlines5 = array.new_line(0)
var pvlines6 = array.new_line(0)
var pvlines7 = array.new_line(0)
var pvlines8 = array.new_line(0)

if barstate.isfirst
    for i = 0 to 61
        array.push(pvlines1, line.new(na, na, na, na))
        array.push(pvlines2, line.new(na, na, na, na))
        array.push(pvlines3, line.new(na, na, na, na))
        array.push(pvlines4, line.new(na, na, na, na))
        array.push(pvlines5, line.new(na, na, na, na))
        array.push(pvlines6, line.new(na, na, na, na))
        array.push(pvlines7, line.new(na, na, na, na))
        array.push(pvlines8, line.new(na, na, na, na))

drawlines(float[] arrin, array<line> linesarr, int startFromBar, color colorin, int linewidth)=>

    skipperpv =  
         array.size(arrin) >= 1000 ? 11 : 
         array.size(arrin) >= 800 ? 10 : 
         array.size(arrin) >= 700 ? 9 : 
         array.size(arrin) >= 600 ? 8 : 
         array.size(arrin) >= 500 ? 7 : 
         array.size(arrin) >= 400 ? 6 : 
         array.size(arrin) >= 300 ? 5 : 
         array.size(arrin) >= 200 ? 4 : 
         array.size(arrin) >= 100 ? 2 : 1

    i = 0
    j = 0 

    while i < array.size(arrin) - skipperpv - 1 - startFromBar
        
        if j > array.size(linesarr) - 1
            break
        pvline = array.get(linesarr, j)
        line.set_xy1(pvline, bar_index - i - skipperpv + startFromBar, array.get(arrin, i + skipperpv + startFromBar))
        line.set_xy2(pvline, bar_index - i + startFromBar, array.get(arrin, i+ startFromBar))
        line.set_color(pvline, colorin)
        if bar_index - i + startFromBar < last_bar_index
            line.set_style(pvline, line.style_solid)
        else 
            line.set_style(pvline, line.style_dotted)

        line.set_width(pvline, linewidth)

        i += skipperpv
        j += 1


specOpsLinearRegression(src, startFromBar, startFromBar + per - 1, aArr)

if barstate.islast  

    temps = array.size(aArr)
    [A, B, R, F] = specOpsFourier(aArr)

    var float[] outbf1 = array.new<float>(temps + startFromBar + 1, 0)
    var float[] outbf2 = array.new<float>(temps + startFromBar + 1, 0)
    var float[] outbf3 = array.new<float>(temps + startFromBar + 1, 0)
    var float[] outbf4 = array.new<float>(temps + startFromBar + 1, 0)
    var float[] outbf5 = array.new<float>(temps + startFromBar + 1, 0)
    var float[] outbf6 = array.new<float>(temps + startFromBar + 1, 0)
    var float[] outbf7 = array.new<float>(temps + startFromBar + 1, 0)
    var float[] outbf8 = array.new<float>(temps + startFromBar + 1, 0)

    for i = 0 to temps - 1
        int ii = i + startFromBar
        float MA = src
        array.set(outbf1, ii, nz(MA + (array.get(A, 1) * math.sin(1 * 2 * math.pi * i / (per - 1)) + array.get(B, 1) * math.cos(1 * 2 * math.pi * i /(per - 1)))))
        array.set(outbf2, ii, nz(MA + (array.get(A, 2) * math.sin(2 * 2 * math.pi * i / (per - 1)) + array.get(B, 2) * math.cos(2 * 2 * math.pi * i /(per - 1)))))
        array.set(outbf3, ii, nz(MA + (array.get(A, 3) * math.sin(3 * 2 * math.pi * i / (per - 1)) + array.get(B, 3) * math.cos(3 * 2 * math.pi * i /(per - 1)))))
        array.set(outbf4, ii, nz(MA + (array.get(A, 4) * math.sin(4 * 2 * math.pi * i / (per - 1)) + array.get(B, 4) * math.cos(4 * 2 * math.pi * i /(per - 1)))))
        array.set(outbf5, ii, nz(MA + (array.get(A, 5) * math.sin(5 * 2 * math.pi * i / (per - 1)) + array.get(B, 5) * math.cos(5 * 2 * math.pi * i /(per - 1)))))
        array.set(outbf6, ii, nz(MA + (array.get(A, 6) * math.sin(6 * 2 * math.pi * i / (per - 1)) + array.get(B, 6) * math.cos(6 * 2 * math.pi * i /(per - 1)))))
        array.set(outbf7, ii, nz(MA + (array.get(A, 7) * math.sin(7 * 2 * math.pi * i / (per - 1)) + array.get(B, 7) * math.cos(7 * 2 * math.pi * i /(per - 1)))))
        array.set(outbf8, ii, nz(MA + (array.get(A, 8) * math.sin(8 * 2 * math.pi * i / (per - 1)) + array.get(B, 8) * math.cos(8 * 2 * math.pi * i /(per - 1)))))

    drawlines(outbf1, pvlines1, startFromBar, freq1col, freq1width)
    drawlines(outbf2, pvlines2, startFromBar, freq2col, freq2width)
    drawlines(outbf3, pvlines3, startFromBar, freq3col, freq3width)
    drawlines(outbf4, pvlines4, startFromBar, freq4col, freq4width)
    drawlines(outbf5, pvlines5, startFromBar, freq5col, freq5width)
    drawlines(outbf6, pvlines6, startFromBar, freq6col, freq6width)
    drawlines(outbf7, pvlines7, startFromBar, freq7col, freq7width)
    drawlines(outbf8, pvlines8, startFromBar, freq8col, freq8width)
