Ehlers Autocorrelation Periodogram [Loxx]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © rumpypumpydumpy

//@version=5
indicator('Ehlers Autocorrelation Periodogram [Loxx]', shorttitle = "EAP [Loxx]", overlay=false, max_bars_back = 3000, timeframe="", timeframe_gaps=true)

book = "2013, Cycle Analytics for Traders"
tasc = "2016, TASC September"

impin = input.string(tasc, "Version of Autocorrelation Periodogram Algorithm", options = [book, tasc], group = "Basic Settings")

auto_src = input.source(close, "Auto Source", group = "Basic Settings")
auto_min = input.int(10, title='Auto Minnimum Length',minval = 1, tooltip = "Default values should be 8, 9, or 10. Very rarely would you change this value.", group = "Basic Settings")
auto_max = input.int(48, title='Auto Maximum Length', minval = 1, tooltip = "Default value is 48. Very rarely would you change this value.", group = "Basic Settings")
auto_avg = input.int(3, title='Auto Average Length', minval = 1, tooltip = "Default value is 3. Very rarely would you change this value.", group = "Basic Settings")
enhances = input.bool(false, title='Enhance Resolution?', group = "UI Options")
widthin = input.int(9, "Line width of thermo plot", group = "UI Options")

_f_hp(_src, int max_len) =>
    c = 360 * math.pi / 180
    _alpha = (1 - math.sin(c / max_len)) / math.cos(c / max_len)
    _hp = 0.0
    _hp := 0.5 * (1 + _alpha) * (_src - nz(_src[1])) + _alpha * nz(_hp[1])
    _hp

_f_ess(_src, int _len) =>
    s = 1.414 //sqrt 2
    _a = math.exp(-s * math.pi / _len)
    _b = 2 * _a * math.cos(s *  math.pi  / _len)
    _c2 = _b
    _c3 = -_a * _a
    _c1 = 1 - _c2 - _c3
    _out = 0.0
    _out := _c1 * (_src + nz(_src[1])) / 2 + _c2 * nz(_out[1], nz(_src[1], _src)) + _c3 * nz(_out[2], nz(_src[2], nz(_src[1], _src)))
    _out

_auto_dom_imp(src, min_len, max_len, ave_len, imp) =>
    c = 2 * math.pi
    s = 1.414

    filt = _f_ess(_f_hp(src, max_len), min_len)
    arr_size = max_len * 2
    
    var corr = array.new_float(arr_size, initial_value=0)
    var cospart = array.new_float(arr_size, initial_value=0)
    var sinpart = array.new_float(arr_size, initial_value=0)
    var sqsum = array.new_float(arr_size, initial_value=0)
    var r1 = array.new_float(arr_size, initial_value=0)
    var r2 = array.new_float(arr_size, initial_value=0)
    var pwr = array.new_float(arr_size, initial_value=0)
    
    for lag = 0 to max_len by 1
        m = ave_len == 0 ? lag : ave_len
        Sx = 0.0
        Sy = 0.0
        Sxx = 0.0
        Syy = 0.0
        Sxy = 0.0
        for i = 0 to m - 1 by 1
            x = nz(filt[i])
            y = nz(filt[lag + i])
            Sx += x
            Sy += y
            Sxx += x * x
            Sxy += x * y
            Syy += y * y
            Syy
        if (m * Sxx - Sx * Sx) * (m * Syy - Sy * Sy) > 0
            array.set(corr, lag, (m * Sxy - Sx * Sy) / math.sqrt((m * Sxx - Sx * Sx) * (m * Syy - Sy * Sy)))
    
    for period = min_len to max_len by 1
        array.set(cospart, period, 0)
        array.set(sinpart, period, 0)
        for n = ave_len to max_len by 1
            array.set(cospart, period, nz(array.get(cospart, period)) + nz(array.get(corr, n)) * math.cos(c * n / period))
            array.set(sinpart, period, nz(array.get(sinpart, period)) + nz(array.get(corr, n)) * math.sin(c * n / period))
        array.set(sqsum, period, math.pow(nz(array.get(cospart, period)), 2) + math.pow(nz(array.get(sinpart, period)), 2))
    
    for period = min_len to max_len by 1
        array.set(r2, period, nz(array.get(r1, period)))
        array.set(r1, period, 0.2 * math.pow(nz(array.get(sqsum, period)), 2) + 0.8 * nz(array.get(r2, period)))

    maxpwr = 0.0
    if imp == book
        maxpwr := 0.995 * maxpwr
        
    for period = min_len to max_len by 1
        if nz(array.get(r1, period)) > maxpwr
            maxpwr := nz(array.get(r1, period))
            
    for period = ave_len to max_len by 1
        array.set(pwr, period, nz(array.get(r1, period)) / maxpwr)

    peakpwr = 0.0
    for period = min_len to max_len by 1
        if imp != book
            if nz(array.get(pwr, period)) > peakpwr 
                peakpwr := nz(array.get(pwr, period))

    spx = 0.0
    sp = 0.0

    for period = min_len to max_len by 1
        if nz(array.get(pwr, period)) >= 0.5
            spx += period * nz(array.get(pwr, period))
            sp += nz(array.get(pwr, period))

    for period = min_len to max_len by 1
        if imp != book
            if peakpwr >= 0.25 and nz(array.get(pwr, period)) >= 0.25
                spx += period * nz(array.get(pwr, period))
                sp += nz(array.get(pwr, period))
        else
            if nz(array.get(pwr, period)) >= 0.5
                spx += period * nz(array.get(pwr, period))
                sp += nz(array.get(pwr, period))
                
    dominantcycle = 0.0
    if imp == book 
        dominantcycle := sp != 0 ? spx / sp : dominantcycle
    else
        dominantcycle := sp != 0 ? spx / sp : dominantcycle
        dominantcycle := sp < 0.25 ? dominantcycle[1] : dominantcycle
        dominantcycle := dominantcycle < 1 ? 1 : dominantcycle

    for period = min_len to max_len by 1
        if enhances
            array.set(pwr, period, math.pow(nz(array.get(pwr, period)), 2))

    [dominantcycle, pwr]
    
[masterdom, normpwr] = _auto_dom_imp(auto_src, auto_min, auto_max, auto_avg, impin) 

//inverse of @RicardoSantos' JohnEhlersFourierTransformlibrary 
_pwr_to_rgb(db, transparency) => //{
    if db > 0.5
        color.rgb(255.0, 255.0* (2 * db - 1), 0.0, transparency)
    else
        color.rgb(255.0 * db , 0.0, 0.0, transparency)

bgcolor(color.rgb(0,0,0,0))
     
auto_min_out = auto_min -1

plot(auto_min_out + 00, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 00), 0.0), linewidth = widthin)
plot(auto_min_out + 02, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 02), 0.0), linewidth = widthin)
plot(auto_min_out + 04, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 04), 0.0), linewidth = widthin)
plot(auto_min_out + 06, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 06), 0.0), linewidth = widthin)
plot(auto_min_out + 08, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 08), 0.0), linewidth = widthin)
plot(auto_min_out + 10, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 10), 0.0), linewidth = widthin)
plot(auto_min_out + 12, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 12), 0.0), linewidth = widthin)
plot(auto_min_out + 14, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 14), 0.0), linewidth = widthin)
plot(auto_min_out + 16, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 16), 0.0), linewidth = widthin)
plot(auto_min_out + 18, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 18), 0.0), linewidth = widthin)
plot(auto_min_out + 20, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 20), 0.0), linewidth = widthin)
plot(auto_min_out + 22, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 22), 0.0), linewidth = widthin)
plot(auto_min_out + 24, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 24), 0.0), linewidth = widthin)
plot(auto_min_out + 26, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 26), 0.0), linewidth = widthin)
plot(auto_min_out + 28, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 28), 0.0), linewidth = widthin)
plot(auto_min_out + 30, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 30), 0.0), linewidth = widthin)
plot(auto_min_out + 32, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 32), 0.0), linewidth = widthin)
plot(auto_min_out + 34, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 34), 0.0), linewidth = widthin)
plot(auto_min_out + 36, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 36), 0.0), linewidth = widthin)
plot(auto_min_out + 38, color = _pwr_to_rgb(array.get(normpwr, auto_min_out + 38), 0.0), linewidth = widthin)


plot(masterdom, linewidth = 5, color = #00008b, title="Dominant Cycle Outline")
plot(masterdom, linewidth = 1, color = color.white, title="Dominant Cycle Line")















