Hodrick-Prescott Extrapolation of Price [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("Hodrick-Prescott Extrapolation of Price [Loxx]", 
     shorttitle = "HPEP [Loxx]", 
     max_lines_count = 500, 
     overlay = true)

greencolor = #2DD204
redcolor = #D2042D 

_lambda(n)=>
    lambda = 0.
    w = 0.
    if (n <= 37)
        w := switch (n) 
            2 => math.pi / 3.0
            3 => math.atan(math.sqrt(0.6))
            4 =>  2.153460564 / n
            5 =>  1.923796031 / n
            6 =>  1.915022415 / n
            7 =>  1.909786299 / n
            8 =>  1.906409362 / n
            9 =>  1.904103844 / n
            10 => 1.902459533 / n
            11 => 1.901245508 / n
            12 => 1.900323600 / n
            13 => 1.899607018 / n
            14 => 1.899038987 / n
            15 => 1.898581041 / n
            16 => 1.898206498 / n
            17 => 1.897896254 / n
            18 => 1.897636390 / n
            19 => 1.897416484 / n
            20 => 1.897228842 / n
            21 => 1.897067382 / n
            22 => 1.896927473 / n
            23 => 1.896805427 / n
            24 => 1.896698359 / n
            25 => 1.896603866 / n
            26 => 1.896520032 / n
            27 => 1.896445477 / n
            28 => 1.896378692 / n
            29 => 1.896318725 / n
            30 => 1.896264646 / n
            31 => 1.896215693 / n
            32 => 1.896171301 / n
            33 => 1.896130841 / n
            34 => 1.896094060 / n
            35 => 1.896060192 / n
            36 => 1.896029169 / n
            37 => 1.896000584 / n
    else
        w := math.pi / 2 / n
    lambda := 0.0625 / math.pow(math.sin(w), 4)
    lambda
    
    
_HPFilter(x, y, lamb, per)=>
    H1 = 0., H2 = 0., H3 = 0., H4 = 0., H5 = 0., 
    HH1 = 0., HH2 = 0., HH3 = 0., HH5 = 0.
    HB= 0., HC= 0., Z= 0.
    
    a = array.new<float>(per, 0.)
    b = array.new<float>(per, 0.)
    c = array.new<float>(per, 0.)
        
    array.set(a, 0, 1.0 + lamb)
    array.set(b, 0, -2.0 * lamb)
    array.set(c, 0, lamb)
    
    for i = 1 to per - 3
        array.set(a, i, 6.0 * lamb + 1.0)
        array.set(b, i, -4.0 * lamb)
        array.set(c, i, lamb)
    
    array.set(a, 1, 5.0 * lamb + 1)
    array.set(a, per - 1, 1.0 + lamb)
    array.set(a, per - 2, 5.0 * lamb + 1.0)
    array.set(b, per - 2, -2.0 * lamb)
    array.set(b, per - 1, 0.)
    array.set(c, per - 2, 0.)
    array.set(c, per - 1, 0.)
    
    for i = 0 to per - 1 
        Z := array.get(a, i) - H4 * H1 - HH5 * HH2
        if (Z == 0) 
            break
            
        HB := array.get(b, i)
        HH1 := H1
        H1 := (HB - H4 * H2) / Z
        array.set(b, i, H1)
        
        HC := array.get(c, i)
        HH2 := H2
        H2 := HC / Z
        array.set(c, i, H2)
        
        array.set(a, i, (array.get(x, i) - HH3 * HH5 - H3 * H4) / Z)
        HH3 := H3
        H3 := array.get(a, i)
        H4 := HB - H5 * HH1
        HH5 := H5
        H5 := HC

    H2 := 0
    H1 := array.get(a, per - 1)
    array.set(y, per - 1, H1)
    
    for i = per - 2 to 0
        array.set(y, i, array.get(a, i) - array.get(b, i) * H1 - array.get(c, i) * H2)
        H2 := H1
        H1 := array.get(y, i)

src = input.source(close, "Source", group = "Basic Settings")
Method = input.int(2, "Method", maxval = 2, minval = 1, group = "Basic Settings")
LastBar = input.int(150, "Last Bar", group = "Basic Settings")
PastBars = input.int(2000, "Past Bars", group = "Basic Settings")
FutBars = input.int(100, "Future Bars", group = "Basic Settings")
colorbars = input.bool(true, "Mute bar colors?", group = "UI Options")

if PastBars <= FutBars
    runtime.error("Error: Past bars must be greater than future bars") 

int n = 0
float[] x = array.new_float(PastBars, 0.)
float[] y = array.new_float(PastBars, 0.)
float[] fv = array.new_float(PastBars, 0.)

var pvlines = array.new_line(0)
var fvlines = array.new_line(0)

if barstate.isfirst
    for i = 0 to 250 - 1
        array.push(pvlines, line.new(na, na, na, na))
        array.push(fvlines, line.new(na, na, na, na))

if barstate.islast
    for i = 0 to PastBars - 1
        array.set(x, i, nz(src[i + LastBar]))
        
    array.set(fv, FutBars, nz(src[LastBar]))
    sum = nz(src[LastBar])
    
    if Method == 2
        n := 2 * FutBars + 1
        _HPFilter(x, y, _lambda(n), PastBars)
        for i = 1 to n - 2 
            sum += nz(src[i + LastBar])
    
    for i = 1 to FutBars 
        if Method == 1
            n := 2 * i + 1
            _HPFilter(x, y, _lambda(n), PastBars)
            sum += nz(src[i + LastBar])
            array.set(fv, FutBars - i, n * array.get(y, 0) - sum)
            sum := n * array.get(y, 0) 
        else
            array.set(fv, FutBars - i, n * array.get(y, FutBars - i) - sum)
            sum += array.get(fv, FutBars - i) - nz(src[n - 1 + LastBar - i])

    xm = array.copy(y)
    skipperpv = array.size(xm) >= 2000 ? 8 : array.size(xm) >= 1000 ? 4 : array.size(xm) >= 500 ? 2 : 1
    int i = 0
    int j = 0

    while i < array.size(xm) - skipperpv
        if j > array.size(pvlines) - 1
            break
        pvline = array.get(pvlines, j)
        line.set_xy1(pvline, bar_index - i - skipperpv - LastBar, array.get(xm, i + skipperpv))
        line.set_xy2(pvline, bar_index - i - LastBar, array.get(xm, i))
        line.set_color(pvline, greencolor)
        line.set_style(pvline, line.style_solid)
        line.set_width(pvline, 3)
        i += skipperpv
        j += 1
    
    i := 0
    j := 0
    
    if Method == 2
    
        var ym = array.new_float(0)
        for ix = 0 to FutBars - 2
            array.push(ym, array.get(fv, ix))
        array.reverse(ym)
        array.insert(ym, 0, array.get(ym, 0))
        array.insert(ym, 0, array.get(ym, 0))

        skipperfv = array.size(ym) >= 2000 ? 8 : array.size(ym) >= 1000 ? 4 : array.size(ym) >= 500 ? 2 : 1

        outer = math.min(250, array.size(ym))
        while i < outer - skipperfv
            if j > array.size(fvlines) - 1
                break
                
            fvline = array.get(fvlines, j)
            line.set_xy1(fvline, bar_index + i + 1 - LastBar, array.get(ym, i + skipperfv))
            line.set_xy2(fvline, bar_index + i + 1 - LastBar - skipperfv, array.get(ym, i))
            line.set_color(fvline, color.blue)
            line.set_style(fvline, line.style_solid)
            line.set_width(fvline, 2)
            
            i += skipperfv
            j += 1

    else
            
        var ym = array.new_float(0)
        for ix = 0 to FutBars 
            array.push(ym, array.get(fv, ix))
        array.reverse(ym)
        skipperfv = array.size(ym) >= 2000 ? 8 : array.size(ym) >= 1000 ? 4 : array.size(ym) >= 500 ? 2 : 1

        
        outer = math.min(250, array.size(ym))
        while i < outer - skipperfv
            if j > array.size(fvlines) - 1
                break
                
            fvline = array.get(fvlines, j)
            line.set_xy1(fvline, bar_index + i + 1 - LastBar, array.get(ym, i + skipperfv))
            line.set_xy2(fvline, bar_index + i + 1 - LastBar - skipperfv, array.get(ym, i))
            line.set_color(fvline, color.blue)
            line.set_style(fvline, line.style_solid)
            line.set_width(fvline, 2)
            
            i += skipperfv
            j += 1

barcolor(colorbars ? color.gray : na)




