Adaptive Ehlers Deviation Scaled Moving Average (AEDSMA)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
//@version=5

indicator("Adaptive Ehlers Deviation Scaled Moving Average (AEDSMA)", shorttitle="MZ AEDSMA", overlay=true)

import MightyZinger/RVSI/1 as mz

timeframe = input.timeframe("", "Timeframe")

uha  =input(true, title="Use Heikin Ashi Candles for Volume Oscillator Calculations")

// Use only Heikinashi Candles for all calculations
haclose = uha ? ohlc4 : close

f_ha_open() =>
    haopen   = float(na)
    haopen := na(haopen[1]) ? (open + close) / 2 : (nz(haopen[1]) + nz(haclose[1])) / 2
    haopen

haopen = uha ? f_ha_open() : open
hahigh = high
halow = low

vol = volume

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////                     Source Options                       //////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

// ─── Different Sources Options List ───► [
string SRC_Tv = 'Use traditional TradingView Sources '
string SRC_Wc = '(high + low + (2 * close)) / 4'
string SRC_Wo = 'close+high+low-2*open'
string SRC_Wi = '(close+high+low) / 3'
string SRC_Ex = 'close>open ? high : low'
string SRC_Hc = 'Heikin Ashi Close'
string src_grp = 'Source Parameters'

// ●───────── Inputs ─────────● {
diff_src = input.string(SRC_Wi, '→ Different Sources Options', options=[SRC_Tv, SRC_Wc, SRC_Wo, SRC_Wi, SRC_Ex, SRC_Hc], group=src_grp)
i_sourceSetup = input.source(close, '  ‍↳ Source Setup', group=src_grp)
uha_src = input.bool(true, title='‍↳ Use Heikin Ashi Candles for Different Source Calculations', group=src_grp)
i_Symmetrical = input.bool(true, '‍↳ Apply Symmetrically Weighted Moving Average at the price source (May Result Repainting)', group=src_grp)

// Heikinashi Candles for calculations
h_close = uha_src ? ohlc4 : close
h_open  = uha_src ? f_ha_open() : open
h_high = high
h_low = low
// Get Source 
src_o = diff_src == SRC_Wc ? (high + low + 2 * h_close) / 4 : diff_src == SRC_Wo ? h_close + h_high + h_low - 2 * h_open : diff_src == SRC_Wi ? (h_close + h_high + h_low) / 3 : diff_src == SRC_Ex ? h_close > h_open ? h_high : h_low : diff_src == SRC_Hc ? ohlc4 : i_sourceSetup

src_f = i_Symmetrical ? ta.swma(src_o) : src_o  // Symmetrically Weighted Moving Average?

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

// AEDSMA Envelop and Signals Checks
showEnv = input.bool(true, title='Show AEDSMA Envelope', group='AEDSMA ENVELOPE DISTANCE ZONE')
mult = input.float(2.7, title='Distance (Envelope) Multiplier', step=.1, group='AEDSMA ENVELOPE DISTANCE ZONE')
env_atr = input.int(40, title='Envelope ATR Length', group='AEDSMA ENVELOPE DISTANCE ZONE')
showSignals = input.bool(true, title='Show Possible Signals', group='Signals Plotting')

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////                     Dynamic Inputs                       //////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

// AEDSMA Parameters
grp1 = 'AEDSMA Parameters'
grp2 = 'Adapt AEDSMA Dynamic Length Based on (Max Length if both left unchecked)'
grp3 = 'Show Signals and AEDSMA Dynamic Coloring Based on'

minLength = input.int(50, title='AEDSMA Minimum Length:', group=grp1)
maxLength = input.int(255, title='AEDSMA Maximum Length:', group=grp1)
adaptPerc = input.float(3.141, minval=0, maxval=100, title='AEDSMA Adapting Percentage:', group=grp1) / 100.0

ssfLength = input.int(title='EDSMA - Super Smoother Filter Length', minval=1, defval=20, group=grp1)
ssfPoles = input.int(title='EDSMA - Super Smoother Filter Poles', defval=2, options=[2, 3], group=grp1)

// AEDSMA Adaptive Length Checks
ch1 = 'Slope'
ch2 = 'Volume'
ch3 = 'Volatility'

volume_chk = input.bool(true, title=ch2, group=grp2, inline='len_chks')
volat_chk = input.bool(true, title=ch3, group=grp2, inline='len_chks')

t_slp_chk = input.bool(true, title=ch1, group=grp3, inline='trd_chks')
t_volume_chk = input.bool(true, title=ch2, group=grp3, inline='trd_chks')
t_volat_chk = input.bool(true, title=ch3, group=grp3, inline='trd_chks')


/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

// Volume and other Inputs
// Volume Oscillator Types Input
osc1 = 'TFS Volume Oscillator'
osc2 = 'On Balance Volume'
osc3 = 'Klinger Volume Oscillator'
osc4 = 'Cumulative Volume Oscillator'
osc5 = 'Volume Zone Oscillator'
osctype = input.string(title='Volume Oscillator Type', group='MZ RVSI Indicator Parameters', defval=osc1, options=[osc1, osc2, osc3, osc4, osc5])

rvsiLen = input.int(14, minval=1, title='RVSI Period', group='MZ RVSI Indicator Parameters')
vBrk = input.int(50, minval=1, title='RVSI Break point', group='MZ RVSI Indicator Parameters')

atrFlength = input.int(14, title='ATR Fast Length', group='Volatility Dynamic Optimization Parameters')
atrSlength = input.int(46, title='ATR Slow Length', group='Volatility Dynamic Optimization Parameters')

slopePeriod = input.int(34, title='Slope Period', group='Slope Dynamic Optimization Parameters')
slopeInRange = input.int(25, title='Slope Initial Range', group='Slope Dynamic Optimization Parameters')
flat = input.int(17, title='Consolidation area is when slope below:', group='Slope Dynamic Optimization Parameters')


/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////                    DSMA Function                         //////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

// Pre-reqs
get2PoleSSF(src, length) =>
    PI = 2 * math.asin(1)
    arg = math.sqrt(2) * PI / length
    a1 = math.exp(-arg)
    b1 = 2 * a1 * math.cos(arg)
    c2 = b1
    c3 = -math.pow(a1, 2)
    c1 = 1 - c2 - c3

    ssf = 0.0
    ssf := c1 * src + c2 * nz(ssf[1]) + c3 * nz(ssf[2])
    ssf

get3PoleSSF(src, length) =>
    PI = 2 * math.asin(1)

    arg = PI / length
    a1 = math.exp(-arg)
    b1 = 2 * a1 * math.cos(1.738 * arg)
    c1 = math.pow(a1, 2)

    coef2 = b1 + c1
    coef3 = -(c1 + b1 * c1)
    coef4 = math.pow(c1, 2)
    coef1 = 1 - coef2 - coef3 - coef4

    ssf = 0.0
    ssf := coef1 * src + coef2 * nz(ssf[1]) + coef3 * nz(ssf[2]) + coef4 * nz(ssf[3])
    ssf

//          MA Main function
dsma(src, len) =>
    float result = 0.0
    zeros = src - nz(src[2])
    avgZeros = (zeros + zeros[1]) / 2

    // Ehlers Super Smoother Filter 
    ssf = ssfPoles == 2 ? get2PoleSSF(avgZeros, ssfLength) : get3PoleSSF(avgZeros, ssfLength)

    // Rescale filter in terms of Standard Deviations
    stdev = ta.stdev(ssf, len)
    scaledFilter = stdev != 0 ? ssf / stdev : 0

    alpha = 5 * math.abs(scaledFilter) / len

    edsma = 0.0
    edsma := alpha * src + (1 - alpha) * nz(edsma[1])
    result := edsma
    result
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////                Dynamic Length Function                   //////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////



dyn_Len(volume_chk, volat_chk, volume_Break, high_Volatility, adapt_Pct, minLength, maxLength) =>
    var float result = math.avg(minLength, maxLength)
    para = volume_chk == true and volat_chk == false ? volume_Break : volume_chk == false and volat_chk == true ? high_Volatility : volume_chk == true and volat_chk == true ? volume_Break and high_Volatility : na
    result := para ? math.max(minLength, result * (1 - adapt_Pct)) : math.min(maxLength, result * (1 + adapt_Pct))
    result

//Slope calculation to determine whether market is in trend, or in consolidation or choppy, or might about to change current trend
calcslope(_ma, src, slope_period, range_1) =>
    pi = math.atan(1) * 4
    highestHigh = ta.highest(slope_period)
    lowestLow = ta.lowest(slope_period)
    slope_range = range_1 / (highestHigh - lowestLow) * lowestLow
    dt = (_ma[2] - _ma) / src * slope_range
    c = math.sqrt(1 + dt * dt)
    xAngle = math.round(180 * math.acos(1 / c) / pi)
    maAngle = dt > 0 ? -xAngle : xAngle
    maAngle


//MA coloring function to mark market dynamics 
dyn_col(slp_chk, volume_chk, volat_chk, slp, _flat, vol_Brk_up, vol_Brk_dn, high_Volatility, col_1, col_2, col_3, col_4, col_r) =>
    slp_up = slp_chk ? slp > _flat : na
    slp_dn = slp_chk ? slp <= -_flat : na
    slp_cons = slp_chk ? slp <= _flat and slp > -flat : na
    
    vol_up = volume_chk ? vol_Brk_up and slp_up : slp_up
    vol_dn = volume_chk ? vol_Brk_dn and slp_dn : slp_dn

    up_voltility = volat_chk ? high_Volatility and vol_up : vol_up
    dn_voltility = volat_chk ? high_Volatility and vol_dn : vol_dn
    
    c1 = up_voltility
    c2 = slp_up
    c3 = slp_cons
    c4 = dn_voltility
    c5 = slp_dn
    col = c1 ? col_1 : c2 ? col_2 : c3 ? col_r : c4 ? col_3 : c5 ? col_4 : c2 == c3 == c5 == na ? col_r : col_r
    col

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////



/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////        Relative Volume Strength Index  (MZ RVSI)         //////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

// TFS Volume Oscillator
tfs_volLen = input.int(30, minval=1,title="Volume Length", group="TFS Volume Oscillator")
// Volume Zone Oscillator
vzo_Len = input.int(21, "VZO Length", minval=1, group="Volume Zone Oscillator Parameters")
//  Volume Oscillator function
kvo_FastX = input.int(34, minval=1,title="Volume Fast Length", group="KVO Parameters")
kvo_SlowX = input.int(55, minval=1,title="Volume Slow Length", group="KVO Parameters")

//  Cumulative Volume Oscillator
cvo(vol_src, rvsiLen, _open, _high, _low, _close) =>
    float result = 0
    ema1len = input.int(defval = 8, title = "EMA 1 Length", minval = 1, group="Cumulative Volume Oscillator Parameters")
    ema2len = input.int(defval = 21, title = "EMA 1 Length", minval = 1, group="Cumulative Volume Oscillator Parameters")
    obvl = "On Balance Volume"
    cvdo = "Cumulative Volume Delta"
    pvlt = "Price Volume Trend"
    cvtype = input.string(defval = pvlt, options = [obvl, cvdo, pvlt], group="Cumulative Volume Oscillator Parameters")
    
    _obv = mz.rvsi_cvo_obv(ema1len, ema2len, rvsiLen)
    _pvt = mz.rvsi_cvo_pvt(ema1len, ema2len, rvsiLen)
    _cvd = mz.rvsi_cvo_cvd(vol_src, ema1len, ema2len, rvsiLen, _open, _high, _low, _close)
    
    result := cvtype == obvl ? _obv : cvtype == cvdo ? _cvd : _pvt
    result


vol_osc(type, vol_src, _rvsiLen, _open, _high, _low, _close) =>
    float _rvsi = 0
    
    if type=="TFS Volume Oscillator"
        _rvsi := mz.rvsi_tfs(vol_src, tfs_volLen, _rvsiLen, _open, _close)
        
    if type=="On Balance Volume"
        _rvsi := mz.rvsi_obv(vol_src, _close, _rvsiLen)
        
    if type=="Klinger Volume Oscillator"
        _rvsi := mz.rvsi_kvo(vol_src, _close, kvo_FastX, kvo_SlowX, _rvsiLen)
        
    if type=="Cumulative Volume Oscillator"
        _rvsi := cvo(vol_src, _rvsiLen, _open, _high, _low, _close)
        
    if type=="Volume Zone Oscillator"
        _rvsi := mz.rvsi_vzo(vol_src, _close, vzo_Len, _rvsiLen)
    _rvsi

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

// RSI of Volume Oscillator Data

rvsi = request.security(syminfo.tickerid, timeframe, vol_osc(osctype,vol,rvsiLen,haopen,hahigh,halow,haclose))

// Volume Breakout Condition
volBrkUp = rvsi > vBrk  
volBrkDn = rvsi < vBrk  

//Volatility Meter
highVolatility = request.security(syminfo.tickerid, timeframe, ta.atr(atrFlength) > ta.atr(atrSlength))

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////


aedsma_dyna = dyn_Len(volume_chk, volat_chk, volBrkUp, highVolatility, adaptPerc, minLength, maxLength)

aedsma = request.security(syminfo.tickerid, timeframe, dsma(src_f, int(aedsma_dyna)))

aedsma_slope = calcslope(aedsma, src_f, slopePeriod, slopeInRange)

up_col = color.lime
dn_col = color.red

aedsma_dyn_col = request.security(syminfo.tickerid, timeframe, dyn_col(t_slp_chk, t_volume_chk, t_volat_chk, aedsma_slope, flat, volBrkUp, volBrkDn, highVolatility, up_col, color.fuchsia, dn_col, color.gray, color.yellow))

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

//Plotting AEDSMA
plot(aedsma, 'MZ AEDSMA', aedsma_dyn_col, 4)
plotchar(aedsma_dyna, 'AEDSMA Dynamic Length', '', location.top, color.new(color.green, 0))
plotchar(rvsi, 'RVSI', '', location.top, color.new(color.red, 0))

// Enveloping AEDSMA with ATR bands

f_warn_col(_close , _low, _high, lower, upper, b_col, t_col, b_warn, t_warn) =>
    var col = color.rgb(170,219,30,60)
    if _low > lower and _high < upper
        col := color.from_gradient(_close, lower, upper, b_col, t_col)
    if _low < lower
        col := b_warn
    if _high > upper 
        col := t_warn
    col

// 1/2 distance zone
env_MA = aedsma
utl =  request.security(syminfo.tickerid, timeframe, env_MA + mult * ta.atr(env_atr))
ltl =  request.security(syminfo.tickerid, timeframe, env_MA - mult * ta.atr(env_atr))
// One distance zone
ttl =  request.security(syminfo.tickerid, timeframe, env_MA + mult * 1.7 * ta.atr(env_atr))
btl =  request.security(syminfo.tickerid, timeframe, env_MA - mult * 1.7 * ta.atr(env_atr))
ttl2 =  request.security(syminfo.tickerid, timeframe, env_MA + mult * 2 * ta.atr(env_atr))
btl2 =  request.security(syminfo.tickerid, timeframe, env_MA - mult * 2 * ta.atr(env_atr))
// 1/2 of the 1/2. for tighter SL or trailing
stp_utl =  request.security(syminfo.tickerid, timeframe, env_MA + mult / 2 * ta.atr(env_atr))
stp_ltl =  request.security(syminfo.tickerid, timeframe, env_MA - mult / 2 * ta.atr(env_atr))

// Plotting AEDSMA Envelope Distance Zone
env_col_s = aedsma_dyn_col
warn_col = request.security(syminfo.tickerid, timeframe, f_warn_col(close , low, high, btl, ttl, color.green, color.red, color.rgb(8,255,8), color.rgb(255,198,0)))
plot(showEnv ? utl : na, color=color.new(warn_col,50))
plot(showEnv ? ltl : na, color=color.new(warn_col,50))
t1 = plot(showEnv ? ttl : na, color=color.new(warn_col,50))
b1 = plot(showEnv ? btl : na, color=color.new(warn_col,50))
t2 = plot(showEnv ? ttl2 : na, color=color.new(warn_col,50))
b2 = plot(showEnv ? btl2 : na, color=color.new(warn_col,50))
s_utl = plot(showEnv ? stp_utl : na, color=na)
s_ltl = plot(showEnv ? stp_ltl : na, color=na)
colEnv = showEnv ? env_col_s : na
fill(s_utl, s_ltl, color=color.new(colEnv, 85))
fill(t1, t2, color=color.new(warn_col,80))
fill(b1, b2, color=color.new(warn_col,80))

// Caution for price crossing outer distance zones of envelop. can act as TP
top_out = high > ttl
bot_out = low < btl
quickWarn = top_out and not top_out[1] or bot_out and not bot_out[1]
upWarn = top_out and not top_out[1]
dnWarn = bot_out and not bot_out[1]

// Signals
_up = aedsma_dyn_col == up_col
_dn = aedsma_dyn_col == dn_col
buy = _up and not _up[1]
sell = _dn and not _dn[1]

var sig = 0
if buy and sig <= 0
    sig := 1
    sig
if sell and sig >= 0
    sig := -1
    sig

longsignal = sig == 1 and (sig != 1)[1]
shortsignal = sig == -1 and (sig != -1)[1]


// Plotting Signals
atrPos = 0.72 * ta.atr(5)
plotshape(showSignals and buy ? aedsma - atrPos : na, style=shape.circle, color=color.new(#AADB1E, 0), location=location.absolute, size=size.tiny)
plotshape(showSignals and sell ? aedsma + atrPos : na, style=shape.circle, color=color.new(#E10600, 0), location=location.absolute, size=size.tiny)

plotshape(showSignals and longsignal ? ltl - atrPos : na, style=shape.triangleup, color=color.new(color.green, 0), location=location.absolute, size=size.small)
plotshape(showSignals and shortsignal ? utl + atrPos : na, style=shape.triangledown, color=color.new(color.red, 0), location=location.absolute, size=size.small)

plotchar(showSignals and upWarn ? high + atrPos : dnWarn ? low - atrPos : na, color=upWarn ? #E10600 : dnWarn ? #AADB1E : na, location=location.absolute, char='⚑', size=size.tiny)

// Alerts
if buy
    alert('Possible Buy Signal at' + str.tostring(close), alert.freq_once_per_bar_close)
if sell
    alert('Possible Sell Signal at' + str.tostring(close), alert.freq_once_per_bar_close)

if longsignal
    alert('Possible Long Term Buy Signal at' + str.tostring(close), alert.freq_once_per_bar_close)
if shortsignal
    alert('Possible Long Term Sell Signal at' + str.tostring(close), alert.freq_once_per_bar_close)

if quickWarn and top_out
    alert('Price has just crossed above top AEDSMA zone', alert.freq_once_per_bar_close)
if quickWarn and bot_out
    alert('Price has just crossed below bottom AEDSMA zone', alert.freq_once_per_bar_close)

// Live Dynamic Length Table on Chart   
var length_table = table(na)
length_table := table.new(position.bottom_right, columns=2, rows=1, border_width=1)
table.cell(length_table, 0, 0, 'AEDSMA Dynamic Length', bgcolor=#cccccc)
table.cell(length_table, 1, 0, str.tostring(int(aedsma_dyna)), bgcolor=#cccccc)

