Adaptive, Relative Strength EMA (RSEMA) [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, Relative Strength EMA (RSEMA) [Loxx]", overlay = true, shorttitle="ARSEMA [Loxx]", timeframe="", timeframe_gaps=true, max_bars_back = 3000)

greencolor = #2DD204  
redcolor = #D2042D 

f1(m) => 
    m >= 0.0 ? m : 0.0
f2(m) => 
    m >= 0.0 ? 0.0 : -m

_chande(mom, len) =>
    m1 = f1(mom)
    m2 = f2(mom)
    cup = 0.0
    cdn = 0.0
    for i = 0 to len-1 by 1
        cup := cup + m1[i]
        cdn := cdn + m2[i]
    [cup, cdn]
    
_jurik_mom(src, len)=>
    suma = 0.0, sumb = 0.0, sumwa = 0.0, sumwb = 0.0
    for k = 0 to len by 1
        weight = len - k 
        suma  += src[k] * weight
        sumb  += src[k] * weight * weight
        sumwa += weight
        sumwb += weight * weight
    out = sumb/sumwb - suma/sumwa

    [cup, cdn] = _chande(out, len)
    [cup, cdn]

_wild(src) =>
    cup = math.max(src - src[1], 0)
    cdn = math.max(src[1] - src, 0)
    [cup, cdn]

rsema(float source = close, int emaPeriod  = 50, int rsPeriod  = 50, float multiplier = 10.0, string type = "Wilder") =>
    mltp1 = 2.0 / (emaPeriod + 1.0)
    coef1 = 2.0 / (rsPeriod + 1.0)
    coef2 = 1.0 - coef1
    
    [cupw, cdnw] = _wild(source)
    [cupj, cdnj] = _jurik_mom(source, rsPeriod)
    [cupc, cdnc] = _chande(ta.change(source), rsPeriod)

    cup  = type == "Wilder Momentum" ? cupw : type == "Jurik Momentum" ? cupj : cupc
    cdn  = type == "Wilder Momentum" ? cdnw : type == "Jurik Momentum" ? cdnj : cdnc
    
    acup = 0.0
    acup := coef1 * cup + coef2 * nz(acup[1])
    acdn = 0.0
    acdn := coef1 * cdn + coef2 * nz(acdn[1])
    
    rs = math.abs(acup - acdn) / (acup + acdn)

    multmor =   multiplier
    rate = mltp1 * (1.0 + rs * multmor)
    rsma = 0.0
    rsma := rate * source + (1.0 - rate) * nz(rsma[1], source)
    rsma
    
_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 = 1.414
    _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 = 1.414
    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) * mult
    dom_in


src   = input.source(close, 'RS and EMA Source', group = "Basic Settings")
type = input.string("Wilder Momentum", "RS Type", options = ["Jurik Momentum", "Chande Momentum", "Wilder Momentum"], group = "Basic Settings")
adaptOn = input.string("Fixed", "RS and EMA Period Input Type", options = ["Fixed", "VHF Adaptive", "Autocorrelation Adaptive"], group =  "Basic Settings")

periods = input.int(50, 'EMA Length', minval=14, group = "Basic Settings")
per_in = input.int(50, 'RS Length', minval=4, group = "Basic Settings")
mltp = input.int(10, 'RS Multiplier', minval=0, group = "Basic Settings")


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


vmaxrsi = ta.highest(src, per_in)
vminrsi = ta.lowest(src, per_in)
noisersi = math.sum(math.abs(ta.change(src)), per_in)

vhfrsi = math.abs(vmaxrsi - vminrsi) / noisersi

vhfperiodrsi = math.floor(-math.log(vhfrsi) * per_in)
vhfperiodrsi := nz(vhfperiodrsi) < 1 ? 1 : nz(vhfperiodrsi)

vmaxema = ta.highest(src, periods)
vminema = ta.lowest(src, periods)
noiseema = math.sum(math.abs(ta.change(src)), periods)

vhfema = math.abs(vmaxema - vminema) / noiseema

vhfperiodema = math.floor(-math.log(vhfema) * periods)
vhfperiodema := nz(vhfperiodema) < 1 ? 1 : nz(vhfperiodema)


masterdom = _auto_dom(auto_src, auto_min, auto_max, auto_avg, 1) 
masterdom := int(masterdom) < 1 ? 1 : int(masterdom)


rsiPeriod = adaptOn == "Fixed" ? per_in : adaptOn == "VHF Adaptive" ?  vhfperiodrsi : int(masterdom)
emaPeriod = adaptOn == "Fixed" ? periods : adaptOn == "VHF Adaptive" ?  vhfperiodema : int(masterdom)


rsema = rsema(src, emaPeriod, rsiPeriod, mltp, type)
plot(rsema, color = close > rsema ? greencolor : redcolor, linewidth = 3)


plotchar(int(masterdom), "Auto Dom Cyle Length", char = "", location = location.top)












