Poly Cycle [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("Poly Cycle [Loxx]", 
     shorttitle="PC [Loxx]", 
     overlay = false, 
     timeframe="", 
     timeframe_gaps = true)

greencolor = #2DD204
redcolor = #D2042D 

sm1 = "Data and Fit"
sm2 = "Analytic Signal"
sm3 = "Phase Shift"
sm4 = "Periods"
sm5 = "RSI"

type = input.string(sm1, "Tool Type", options = [sm1, sm2, sm3, sm4, sm5], group = "Basic Settings")
src  = input.source(hl2, "Source",  group = "Basic Settings")
per = input.int(17, "Period",  group = "Basic Settings")
dgr = input.int(4, "Degree",  group = "Basic Settings")

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

HilbertWidth = 7
Width = math.floor((per - 1.0) / 2.0)
DataSize = 2 * Width + 1

Polynomial = array.new<float>(DataSize * (dgr + 1) + 1, 0.)
Coefficient = array.new<float>(dgr + 1, 0.)
Cycle = array.new<float>(DataSize, 0.)
Signal = array.new<float>(DataSize, 0.)
Hilbert = array.new<float>(DataSize, 0.)
HilbertTransform = array.new<float>(2 * HilbertWidth + 1, 0.)
SignalTransform = array.new<float>(2 * HilbertWidth + 1, 0.)

for k = -HilbertWidth to HilbertWidth 
    if (k % 2.0 == 0)
        array.set(HilbertTransform, HilbertWidth + k, 0.)
    else
        array.set(HilbertTransform, HilbertWidth + k, 2.0 / (math.pi * k))
    if (k == 0)
        array.set(SignalTransform, HilbertWidth + k, 1.)
    else
        array.set(SignalTransform, HilbertWidth + k, 0.)
    
for k = -Width to Width
    array.set(Polynomial, 0 * DataSize + Width + k, 1.)
if (dgr >= 1)
    for k = -Width to Width
        array.set(Polynomial, 1 * DataSize + Width + k, k)
      
if (dgr > 1) 
    for p = 1 to dgr - 1
        for k = -Width to Width 
            temp1 = k * (2.0 * p + 1.0) / (p + 1.0)
            temp2 = -(p / (p + 1.0)) * (2.0 * Width + 1 + p) * (2.0 * Width + 1 - p) / 4.0
            array.set(Polynomial, (p + 1) * DataSize + Width + k, temp1 * array.get(Polynomial, p * DataSize + Width + k) + temp2 * array.get(Polynomial, (p - 1) * DataSize + Width + k))

for p = 0 to dgr 
    temp = math.pow(2, -2 * p) / (2.0 * p + 1.0)
    for k = -p to p 
        temp *= (2.0 * Width + 1.0 + k)
    if (temp > 0) 
        temp := 1 / math.sqrt(temp)
    for k = -Width to Width 
        array.set(Polynomial, p * DataSize + Width + k, temp * array.get(Polynomial, p * DataSize + Width + k))

PhaseShift = 0.
_price = 1

radToDegrees = 180.0 / math.pi
EarliestWidth = Width - 2.0 * HilbertWidth - 1.0

for p = 0 to dgr
    array.set(Coefficient, p, 0.) 
    for j = -Width to Width 
        temp = array.get(Coefficient, p)
        array.set(Coefficient, p, temp + array.get(Polynomial, p * DataSize + int(Width + j)) * nz(src[int(Width + j)]))

DC = array.get(Coefficient, 0) * array.get(Polynomial, Width + Width)

for j = EarliestWidth to Width 
    array.set(Cycle, int(Width + j), 0.)
    for p = 1 to dgr
        tmp = array.get(Cycle, int(Width + j))
        array.set(Cycle, int(Width + j), tmp + array.get(Coefficient, p) * array.get(Polynomial, p * DataSize + int(Width + j)))
for j = EarliestWidth + HilbertWidth to  Width - HilbertWidth
    array.set(Signal, int(Width + j), 0.)
    array.set(Hilbert, int(Width + j), 0.)
    for k = -HilbertWidth to HilbertWidth
        tmphil = array.get(Hilbert,int(Width + j))
        tmpsig = array.get(Signal, int(Width + j))
        array.set(Hilbert, int(Width + j), tmphil + array.get(HilbertTransform, HilbertWidth + k) * array.get(Cycle, int(Width + j) + k))
        array.set(Signal, int(Width + j), tmpsig + array.get(SignalTransform, HilbertWidth + k) * array.get(Cycle, int(Width + j) + k))
    
    temp = math.pow(array.get(Signal, int(Width + j)), 2) + math.pow(array.get(Hilbert, int(Width + j)), 2)
    if (temp > 0) 
        temp := math.pow(temp, -0.5)
        tmpsig = array.get(Signal, int(Width + j))
        tmphil = array.get(Hilbert, int(Width + j))
        array.set(Signal, int(Width + j), temp * tmpsig)
        array.set(Hilbert, int(Width + j), temp * tmphil)

T1 = array.get(Signal, 2 * Width - HilbertWidth) * 
     array.get(Signal, 2 * Width - HilbertWidth - 1) + 
     array.get(Hilbert, 2 * Width - HilbertWidth) *
     array.get(Hilbert, 2 * Width - HilbertWidth - 1)
     
     
T2 = -array.get(Hilbert, 2 * Width - HilbertWidth) *
     array.get(Signal, 2 * Width - HilbertWidth - 1) +
     array.get(Signal, 2 * Width - HilbertWidth) * 
     array.get(Hilbert, 2 * Width - HilbertWidth - 1)

PhaseShift := 0.
if (T1 != 0)
    PhaseShift := math.atan(T2 / T1) * radToDegrees
else 
    PhaseShift := nz(PhaseShift[1])

temp = 0.
j = 0., k = 0.
while math.abs(temp) < 360 and j < bar_index
    temp += nz(PhaseShift[j])
    j += 1
    
period = j
temp := 0

while math.abs(temp) < 180 and k < bar_index
    temp += nz(PhaseShift[k])
    k += 1

halfPeriod = k

LeadingSignalEdge = array.get(Signal, Width + Width - HilbertWidth)
LeadingHilbertEdge = array.get(Hilbert, Width + Width - HilbertWidth)
LeadingCycleEdge = array.get(Cycle, Width + Width)

value1 = 0.
value2 = 0.
switch type 
    sm1 =>
        value1 := src - DC
        value2 := LeadingCycleEdge
    sm2 =>
        value1 := LeadingSignalEdge
        value2 := LeadingHilbertEdge
    sm3 =>
        if (T2 > 0) 
            value1 := PhaseShift
        if (T2 < 0) 
            value2 := PhaseShift
    sm4 =>
        value1 := period
        value2 := halfPeriod
    sm5 =>
        int count = math.floor(period + HilbertWidth + 1)
        smax = src
        smin = src
        smax := ta.highest(src,  count)
        smin := ta.lowest(src,  count)
        if (smax != smin) 
            value1 := -1 + 2 * (src - smin) / (smax - smin)
    =>
        value1 := 0.
        value2 := 0.

plot(type != sm3 ? value1 : na, color = type == sm5 ? (value1 > 0 ? greencolor : redcolor) : greencolor, linewidth = 2)
plot(type != sm5 and type != sm3 ? value2 : na, color = redcolor, linewidth = 2)

plot(type != sm4 and type != sm3 ? 0. : na, color = bar_index % 2 ? color.gray : na)

plotshape(type == sm3 and value1 != 0. ? value1 : na, color = greencolor, location = location.absolute)
plotshape(type == sm3 and value2 != 0. ? value2 : na, color = redcolor, location = location.absolute)

colorout = type == sm5 ? (value1 > 0 ? greencolor : redcolor) : value1 > value2 ? greencolor : redcolor

barcolor(colorbars ? type != sm3 and type != sm4 ? colorout : na : na)














