Adaptive Trend Cipher 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('Adaptive Trend Cipher [Loxx]', shorttitle = "ATC [Loxx]", overlay = true, max_bars_back = 5000)

selector = input.string("Band-pass", title='Dominant Cycle Type', options = ["Autocorrelation", "Instantaneous", "Hilbert", "Band-pass", "Dual Differentiator"], group = "Trend Core Settings")
corr_src = input.source(close, title='Trend Correlation Source', group = "Trend Core Settings")
cycle_mult = input.float(100.0, minval = 1, title = "Dominant Cycle Length (%)", group = "Trend Core Settings")/100

auto_src = input.source(close, title='Auto Source', group = "Autocorrelation")
auto_min = input.int(8, minval = 1, title='Auto Min Length', group = "Autocorrelation")
auto_max = input.int(48, minval = 1, title='Auto Max Length', group = "Autocorrelation")
auto_avg = input.int(3, minval = 1, title='Auto Average Length', group = "Autocorrelation")

hilbert_src = input.source(hl2, title='Hilbert Source', group = "Regular Hilbert Period")
hilbert_len = input.int(7, title='Hilbert Length', minval=1, group = "Regular Hilbert Period")
hilbert_alpha = input.float(0.07, title = "Hilbert Alpha", step = 0.01, group = "Regular Hilbert Period")

instant_src = input.source(hl2, title='Instant Source', group = "Instantaneous")
instant_min_len = input.int(8, title='Instant Min Length', minval=1, group = "Instantaneous")
instant_max_len = input.int(48, title='Instant Max Length', minval=1, group = "Instantaneous")

bp_period = input.int(20, "Band-pass Period", minval = 1, group = "Band-pass")
Bandwidth = input.float(0.70, "Band-pass Width", step = 0.1, group = "Band-pass")

LPPeriod = input(20, title='LP Period', group = "Hilbert Dual Differentiator")
min_len_input = input.int(8, title='Min Length', group = "Hilbert Dual Differentiator")
max_len_input = input.int(48, title='Max Length', group = "Hilbert Dual Differentiator")

_hilber_dual(len_in, min_len, max_len, mult) =>
    alpha1 = 0.00
    HP = 0.00
    a1 = 0.00
    b1 = 0.00
    c1 = 0.00
    c2 = 0.00
    c3 = 0.00
    Filt = 0.00
    QFilt = 0.00
    Real = 0.00
    Quad = 0.00
    Imag = 0.00
    IPeak = 0.00
    QPeak = 0.00
    IDot = 0.00
    QDot = 0.00
    Period = 0.00
    DomCycle = 0.00
    
    alpha1 := (math.cos(0.707 * 2 * math.pi / max_len) + math.sin(0.707 * 2 * math.pi / max_len) - 1) / math.cos(0.707 * 2 * math.pi / max_len)
    HP := (1 - alpha1 / 2) * (1 - alpha1 / 2) * (close - 2 * close[1] + close[2]) + 2 * (1 - alpha1) * nz(HP[1]) - (1 - alpha1) * (1 - alpha1) * nz(HP[2])
    
    a1 := math.exp(-math.sqrt(2) * math.pi / LPPeriod)
    b1 := 2 * a1 * math.cos(math.sqrt(2) * math.pi / LPPeriod)
    c2 := b1
    c3 := -a1 * a1
    c1 := 1 - c2 - c3
    
    Filt := c1 * (HP + nz(HP[1])) / 2 + c2 * nz(Filt[1]) + c3 * nz(Filt[2])
    
    IPeak := 0.991 * nz(IPeak[1])
    IPeak := math.abs(Filt) > IPeak ? math.abs(Filt) : IPeak
    
    Real := Filt / IPeak
    Quad := Real - nz(Real[1])
    QPeak := 0.991 * nz(QPeak[1])
    QPeak := math.abs(Quad) > QPeak ? math.abs(Quad) : QPeak
    Imag := Quad / QPeak
    IDot := Real - nz(Real[1])
    QDot := Imag - nz(Imag[1])
    
    Period := Real * QDot - Imag * IDot != 0 ? 2 * math.pi * (Real * Real + Imag * Imag) / (-Real * QDot + Imag * IDot) : Period
    
    Period := math.min(math.max(Period, min_len), max_len)

    DomCycle := c1 * (Period + nz(Period[1])) / 2 + c2 * nz(DomCycle[1]) + c3 * nz(DomCycle[2])
    out = DomCycle * mult
    out

_corrrelation(x, y, len) => 
    lenMinusOne = len - 1
    meanx = 0.0, meany = 0.0
    for i=0.0 to lenMinusOne
        meanx := meanx + nz(x[i])
        meany := meany + nz(y[i])
    meanx := meanx / len
    meany := meany / len
    sumxy=0.0, sumx=0.0, sumy=0.0
    for i=0  to lenMinusOne
        sumxy := sumxy +    (nz(x[i]) - meanx) * (nz(y[i]) - meany)
        sumx  := sumx  + math.pow(nz(x[i]) - meanx, 2)
        sumy  := sumy  + math.pow(nz(y[i]) - meany, 2)
    sumxy / math.sqrt(sumy * sumx)

_cycleit(src, len, len_of_cycle, alpha) =>
    instPeriod = 0.0
    smooth = (src + 2 * nz(src[1]) + 2 * nz(src[2]) + nz(src[3])) / 6
    cycle = 0.0
    cycle := bar_index < 7 ? (src - 2 * nz(src[1]) + nz(src[2])) / 4 : 
         (1 - .5*alpha)*(1 - .5*alpha)*(smooth - 2*smooth[1] + smooth[2]) + 2*(1 - alpha)*cycle[1] - (1 - alpha)*(1 - alpha)*cycle[2]
    q1 = (0.0962 * cycle + 0.5769 * nz(cycle[2]) - 0.5769 * nz(cycle[4]) - 0.0962 * nz(cycle[6])) * (0.5 + 0.08 * nz(instPeriod[1]))
    i1 = nz(cycle[3])
    deltaPhase = q1 != 0 and nz(q1[1]) != 0 ? (i1 / q1 - nz(i1[1]) / nz(q1[1])) / (1 + i1 * nz(i1[1]) / (q1 * nz(q1[1]))) : 0
    deltaPhase := math.min(math.max(deltaPhase, 0.1), 1.1)
    medianDelta = ta.percentile_nearest_rank(deltaPhase, len, 50)
    dc = medianDelta != 0 ? math.pi*2 / medianDelta + 0.5 : 15
    instPeriod := 0.33 * dc + 0.67 * nz(instPeriod[1])
    period = 0.0
    period := 0.15 * instPeriod + 0.85 * nz(period[1])
    number = math.round(math.floor(len_of_cycle*ta.wma(period, 4)))
    number
 
_f_hp(_src, max_len) =>
    var 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, _len) =>
    var s = math.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(src, min_len, max_len, ave_len, mult) =>
    var c = 2 * math.pi
    var s = math.sqrt(2)
    avglen = ave_len

    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 = avglen == 0 ? lag : avglen
        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
    
    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)
    
    dominantcycle = 0.0
    peakpwr = 0.0
    
    for period = min_len to max_len by 1
        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 peakpwr >= 0.25 and nz(array.get(pwr, period)) >= 0.25
            spx += period * nz(array.get(pwr, period))
            sp += nz(array.get(pwr, period))

    if sp != 0 
        dominantcycle := spx / sp
        
    if sp < 0.25 
        dominantcycle := dominantcycle[1]
        
    if dominantcycle < 1 
        dominantcycle := 1

    dom_in = math.min(math.max(dominantcycle, min_len), max_len)
    dom_out = dom_in * mult
    dom_out

    
_instant_cycle(src, llen, hlen, mult) =>
    v1 = src - nz(src[llen])
    ip = 0.0
    ip := 1.25 * (nz(v1[4]) - 0.635 * nz(v1[2])) + 0.635 * nz(ip[3])
    qu = 0.0
    qu := nz(v1[2]) - 0.338 * v1 + 0.338 * nz(qu[2])
    phase = math.abs(ip + nz(ip[1])) > 0 ? math.atan(math.abs((qu + nz(qu[1])) / (ip + nz(ip[1])))) * 180 / math.pi : 0
    phase := ip < 0 and qu > 0 ? 180 - phase : phase
    phase := ip < 0 and qu < 0 ? 180 + phase : phase
    phase := ip > 0 and qu < 0 ? 360 - phase : phase
    dPhase = nz(phase[1]) - phase
    dPhase := nz(phase[1]) < 90 and phase > 270 ? 360 + nz(phase[1]) - phase : dPhase
    dPhase := math.max(math.min(60, dPhase), 1)
    instPeriod = 0.0
    v4 = 0.0
    for i = 0 to hlen by 1
        v4 += nz(dPhase[i])
        if v4 > 360 and instPeriod == 0
            instPeriod := i
            instPeriod
    dcPeriod = 0.0
    dcPeriod := 0.25 * instPeriod + 0.75 * nz(dcPeriod[1])
    dom_in = math.min(math.max(dcPeriod, llen), hlen)
    dom_out = dom_in * mult
    dom_out

_bpDom(len, bpw, mult) =>
    HP = 0.0
    BP = 0.0
    Peak = 0.0
    Real = 0.0
    counter = 0.0
    DC = 0.0
    alpha2 = (math.cos(0.25 * bpw * 2 * math.pi / len) + math.sin(0.25 * bpw * 2 * math.pi  / len) - 1) / math.cos(0.25 * bpw * 2 * math.pi / len)
    HP := (1 + alpha2 / 2) * (close - nz(close[1])) + (1 - alpha2) * nz(HP[1])
    beta1 = math.cos(2 * math.pi / len)
    gamma1 = 1 / math.cos(2 *  math.pi * bpw / len)
    alpha1 = gamma1 - math.sqrt(gamma1 * gamma1 - 1)
    BP := 0.5 * (1 - alpha1) * (HP - nz(HP[2])) + beta1 * (1 + alpha1) * nz(BP[1]) - alpha1 * nz(BP[2])
    BP := bar_index == 1 or bar_index == 2  ? 0 : BP
    Peak := 0.991 * Peak
    Peak := math.abs(BP) > Peak ? math.abs(BP) : Peak
    Real := Peak != 0 ?  BP / Peak : Real
    DC := nz(DC[1])
    DC := DC < 6 ? 6 : DC
    counter := counter[1] + 1
    if ta.crossover(Real, 0) or ta.crossunder(Real, 0)
        DC := 2 * counter
        if 2 * counter > 1.25 * nz(DC[1])
            DC := 1.25 * DC[1]
        if 2 * counter < 0.8 * nz(DC[1])
            DC := 0.8 * nz(DC[1])
        counter := 0
    temp_out = mult * DC
    temp_out

_corr_period(type) =>
    int out = 0
    if type == "Autocorrelation"
        auto = _auto_dom(auto_src, auto_min, auto_max, auto_avg, cycle_mult)
        out := math.floor(auto) < 1 ? 1 : math.floor(auto)
    if type == "Instantaneous"
        instant = _instant_cycle(instant_src, instant_min_len, instant_max_len, cycle_mult)
        out := math.floor(instant) < 1 ? 1 : math.floor(instant) 
    if type == "Hilbert"
        hilbert = _cycleit(hilbert_src, hilbert_len, cycle_mult, hilbert_alpha)
        out := math.floor(hilbert) < 1 ? 1 : math.floor(hilbert) 
    if type == "Band-pass"
        band = _bpDom(bp_period, Bandwidth, cycle_mult)
        out := math.floor(band) < 1 ? 1 : math.floor(band)
    if type == "Dual Differentiator"
        hilbert_dd = _hilber_dual(LPPeriod, min_len_input, max_len_input, cycle_mult)
        out := math.floor(hilbert_dd) < 1 ? 1 : math.floor(hilbert_dd)
    out

corr_period = _corr_period(selector)

correlate = _corrrelation(corr_src, bar_index, corr_period)

colorCorrelate = 
     correlate > 0.95 ?  #FFF300 : 
     correlate > 0.9 ?  #80ff5f : 
     correlate > 0.8 ?   #58ff2e : 
     correlate > 0.6 ?  #33fc00 : 
     correlate > 0.4 ?   #29cc00 : 
     correlate > 0.2 ?  #1f9b00 : 
     correlate > 0.0 ?   #156a00 : 
     correlate < -0.95 ? #FF0EF3 : 
     correlate < -0.9 ? #ff2e57 : 
     correlate < -0.8 ?  #fc0031 : 
     correlate < -0.6 ? #cc0028 : 
     correlate < -0.4 ?  #9b001e : 
     correlate < -0.2 ? #6a0014 : 
     #6a0014 

color_out = color.new(colorCorrelate, 0)
barcolor(color_out)














