mean-reversion-channel-mri-variant.pine
Copy
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © goodgains
// REUSING THIS CODE: You are welcome to reuse this code without permission, including in closed-source publications, as long as proper credits are given :)
// Description :
// Mean Reversion Channel objective, based on Mean Reversion theory (everything has a tendency to revert back to its mean), is to help visualizing:
// Inner Channel -> Dynamic Support and Resistance
// Outer Channel -> Overbought/Oversold Zone which may signal consolidation phase or potential reversal due to unsustainable move
// The concept of this indicator oriiginally derived from Keltner Channel (The Keltner Channel was first introduced by Chester Keltner in the 1960s. The original formula used simple moving averages (SMA) and the high-low price range to calculate the bands. In the 1980s, a new formula was introduced that used ATR.)
// Instead if using SMA/EMA, this indicator used SuperSmoother MA as it's mean with longer lookback period (default to 200) to get more stable channel line. i also added second level so the indicator will have inner and outer channel
// Details of each filtering mrc_type used for mean calculation can be read in Ehlers Technical Papers: "Swiss Army Knife Indicator" and/or his book "Cybernetics Analysis for Stock and Futures"
// Disclaimer:
// Past performance is not an indicator of future results.
// My opinions are my own and do not constitute financial advice in any way whatsoever.
// Nothing published by me constitutes an investment/trading recommendation, nor should any data or Content published by me be relied upon for any investment/trading activities.
// I strongly recommends that you perform your own independent research and/or speak with a qualified investment professional before making any financial decisions.
// Any ideas to further improve this indicator are welcome :)
//@version=5
indicator('Mean Reversion Channel - MRI Variant', shorttitle='MRC', overlay=true, format=format.inherit)
// @keywords: mean-reversion-channel-mri-variant, mrc, supersmoother-mean, inner-outer-channel, mean-reversion-bands, overbought-oversold-zone, dynamic-support-resistance, multi-timeframe-table, ehlers-filter-types, channel-zone-fill, line-extension, butterworth-gaussian-filter, bandstop-filter, mtf-analysis-panel, inherit-format-overlay, channel-multiplier, price-source-hlc3, zone-transparency, draw-channel-zones, stable-mean-line
//************************************************************************************************************
// Parameter
//************************************************************************************************************
indiSet = input(false, '═════════ MRC Parameter ════════')
source = input(hlc3, title='Price Source')
mrc_type = input.string('SuperSmoother', title='Filter Type', options=['SuperSmoother', 'Ehlers EMA', 'Gaussian', 'Butterworth', 'BandStop', 'SMA', 'EMA', 'RMA'])
length = input.int(200, title='Lookback Period', minval=1)
innermult = input.float(1.0, title='Inner Channel Size Multiplier', minval=0.1)
outermult = input.float(2.415, title='Outer Channel Size Multiplier', minval=0.1)
ChartSet = input(false, '═════════ Chart Setting ════════')
drawchannel = input(true, title='Draw Channel')
displayzone = input(true, title='Draw Zone (With Channel)')
zonetransp = input.int(60, title='Zone Transparency', minval=0, maxval=100)
displayline = input(true, title='Display Line Extension')
MTFSet = input(false, '═════════ MTF Setting ════════')
enable_mtf = input(true, title='Enable Multiple TimeFrame Analysis')
mtf_disp_typ = input.string('On Hover', title='MTF Display Type', options=['Always Display', 'On Hover'])
mtf_typ = input.string('Auto', title='Multiple TimeFrame Type', options=['Auto', 'Custom'])
mtf_lvl1 = input.timeframe('D', title='Custom MTF Level 1')
mtf_lvl2 = input.timeframe('W', title='Custom MTF Level 2')
//************************************************************************************************************
// Functions Start {
//************************************************************************************************************
var pi = 2 * math.asin(1)
var mult = pi * innermult
var mult2 = pi * outermult
var gradsize = 0.5
var gradtransp = zonetransp
//-----------------------
// Ehler SwissArmyKnife Function
//-----------------------
SAK_smoothing(_type, _src, _length) =>
c0 = 1.0
c1 = 0.0
b0 = 1.0
b1 = 0.0
b2 = 0.0
a1 = 0.0
a2 = 0.0
alpha = 0.0
beta = 0.0
gamma = 0.0
cycle = 2 * pi / _length
if _type == 'Ehlers EMA'
alpha := (math.cos(cycle) + math.sin(cycle) - 1) / math.cos(cycle)
b0 := alpha
a1 := 1 - alpha
a1
if _type == 'Gaussian'
beta := 2.415 * (1 - math.cos(cycle))
alpha := -beta + math.sqrt(beta * beta + 2 * beta)
c0 := alpha * alpha
a1 := 2 * (1 - alpha)
a2 := -(1 - alpha) * (1 - alpha)
a2
if _type == 'Butterworth'
beta := 2.415 * (1 - math.cos(cycle))
alpha := -beta + math.sqrt(beta * beta + 2 * beta)
c0 := alpha * alpha / 4
b1 := 2
b2 := 1
a1 := 2 * (1 - alpha)
a2 := -(1 - alpha) * (1 - alpha)
a2
if _type == 'BandStop'
beta := math.cos(cycle)
gamma := 1 / math.cos(cycle * 2 * 0.1) // delta default to 0.1. Acceptable delta -- 0.05<d<0.5
alpha := gamma - math.sqrt(gamma * gamma - 1)
c0 := (1 + alpha) / 2
b1 := -2 * beta
b2 := 1
a1 := beta * (1 + alpha)
a2 := -alpha
a2
if _type == 'SMA'
c1 := 1 / _length
b0 := 1 / _length
a1 := 1
a1
if _type == 'EMA'
alpha := 2 / (_length + 1)
b0 := alpha
a1 := 1 - alpha
a1
if _type == 'RMA'
alpha := 1 / _length
b0 := alpha
a1 := 1 - alpha
a1
_Input = _src
_Output = 0.0
_Output := c0 * (b0 * _Input + b1 * nz(_Input[1]) + b2 * nz(_Input[2])) + a1 * nz(_Output[1]) + a2 * nz(_Output[2]) - c1 * nz(_Input[_length])
_Output
//-----------------------
// SuperSmoother Function
//-----------------------
supersmoother(_src, _length) =>
s_a1 = math.exp(-math.sqrt(2) * pi / _length)
s_b1 = 2 * s_a1 * math.cos(math.sqrt(2) * pi / _length)
s_c3 = -math.pow(s_a1, 2)
s_c2 = s_b1
s_c1 = 1 - s_c2 - s_c3
ss = 0.0
ss := s_c1 * _src + s_c2 * nz(ss[1], _src[1]) + s_c3 * nz(ss[2], _src[2])
ss
//-----------------------
// Auto TimeFrame Function
//-----------------------
// ————— Converts current chart resolution into a float minutes value.
f_resInMinutes() =>
_resInMinutes = timeframe.multiplier * (timeframe.isseconds ? 1. / 60 : timeframe.isminutes ? 1. : timeframe.isdaily ? 60. * 24 : timeframe.isweekly ? 60. * 24 * 7 : timeframe.ismonthly ? 60. * 24 * 30.4375 : na)
_resInMinutes
get_tf(_lvl) =>
y = f_resInMinutes()
z = timeframe.period
if mtf_typ == 'Auto'
if y < 1
z := _lvl == 1 ? '1' : _lvl == 2 ? '5' : z
z
else if y <= 3
z := _lvl == 1 ? '5' : _lvl == 2 ? '15' : z
z
else if y <= 10
z := _lvl == 1 ? '15' : _lvl == 2 ? '60' : z
z
else if y <= 30
z := _lvl == 1 ? '60' : _lvl == 2 ? '240' : z
z
else if y <= 120
z := _lvl == 1 ? '240' : _lvl == 2 ? 'D' : z
z
else if y <= 240
z := _lvl == 1 ? 'D' : _lvl == 2 ? 'W' : z
z
else if y <= 1440
z := _lvl == 1 ? 'W' : _lvl == 2 ? 'M' : z
z
else if y <= 10080
z := _lvl == 1 ? 'M' : z
z
else
z := z
z
else
z := _lvl == 1 ? mtf_lvl1 : _lvl == 2 ? mtf_lvl2 : z
z
z
//-----------------------
// Mean Reversion Channel Function
//-----------------------
get_mrc() =>
v_condition = 0
v_meanline = source
v_meanrange = supersmoother(ta.tr, length)
//-- Get Line value
if mrc_type == 'SuperSmoother'
v_meanline := supersmoother(source, length)
v_meanline
if mrc_type != 'SuperSmoother'
v_meanline := SAK_smoothing(mrc_type, source, length)
v_meanline
v_upband1 = v_meanline + v_meanrange * mult
v_loband1 = v_meanline - v_meanrange * mult
v_upband2 = v_meanline + v_meanrange * mult2
v_loband2 = v_meanline - v_meanrange * mult2
//-- Check Condition
if close > v_meanline
v_upband2_1 = v_upband2 + v_meanrange * gradsize * 4
v_upband2_9 = v_upband2 + v_meanrange * gradsize * -4
if high >= v_upband2_9 and high < v_upband2
v_condition := 1
v_condition
else if high >= v_upband2 and high < v_upband2_1
v_condition := 2
v_condition
else if high >= v_upband2_1
v_condition := 3
v_condition
else if close <= v_meanline + v_meanrange
v_condition := 4
v_condition
else
v_condition := 5
v_condition
if close < v_meanline
v_loband2_1 = v_loband2 - v_meanrange * gradsize * 4
v_loband2_9 = v_loband2 - v_meanrange * gradsize * -4
if low <= v_loband2_9 and low > v_loband2
v_condition := -1
v_condition
else if low <= v_loband2 and low > v_loband2_1
v_condition := -2
v_condition
else if low <= v_loband2_1
v_condition := -3
v_condition
else if close >= v_meanline + v_meanrange
v_condition := -4
v_condition
else
v_condition := -5
v_condition
[v_meanline, v_meanrange, v_upband1, v_loband1, v_upband2, v_loband2, v_condition]
//-----------------------
// MTF Analysis
//-----------------------
get_stat(_cond) =>
ret = 'Price at Mean Line\n'
if _cond == 1
ret := 'Overbought (Weak)\n'
ret
else if _cond == 2
ret := 'Overbought\n'
ret
else if _cond == 3
ret := 'Overbought (Strong)\n'
ret
else if _cond == 4
ret := 'Price Near Mean\n'
ret
else if _cond == 5
ret := 'Price Above Mean\n'
ret
else if _cond == -1
ret := 'Oversold (Weak)\n'
ret
else if _cond == -2
ret := 'Oversold\n'
ret
else if _cond == -3
ret := 'Oversold (Strong)\n'
ret
else if _cond == -4
ret := 'Price Near Mean\n'
ret
else if _cond == -5
ret := 'Price Below Mean\n'
ret
ret
//-----------------------
// Chart Drawing Function
//-----------------------
format_price(x) =>
y = str.tostring(x, '0.00000')
if x > 10
y := str.tostring(x, '0.000')
y
if x > 1000
y := str.tostring(x, '0.00')
y
y
f_PriceLine(_ref, linecol) =>
line.new(x1=bar_index, x2=bar_index - 1, y1=_ref, y2=_ref, extend=extend.left, color=linecol)
f_MTFLabel(_txt, _yloc) =>
label.new(x=time + math.round(ta.change(time) * 20), y=_yloc, xloc=xloc.bar_time, text=mtf_disp_typ == 'Always Display' ? _txt : 'Check MTF', tooltip=mtf_disp_typ == 'Always Display' ? '' : _txt, color=color.black, textcolor=color.white, size=size.normal, style=mtf_disp_typ == 'On Hover' and displayline ? label.style_label_lower_left : label.style_label_left, textalign=text.align_left)
//} Function End
//************************************************************************************************************
// Calculate Channel
//************************************************************************************************************
var tf_0 = timeframe.period
var tf_1 = get_tf(1)
var tf_2 = get_tf(2)
[meanline, meanrange, upband1, loband1, upband2, loband2, condition] = get_mrc()
[mtf1_meanline, mtf1_meanrange, mtf1_upband1, mtf1_loband1, mtf1_upband2, mtf1_loband2, mtf1_condition] = request.security(syminfo.tickerid, tf_1, get_mrc())
[mtf2_meanline, mtf2_meanrange, mtf2_upband1, mtf2_loband1, mtf2_upband2, mtf2_loband2, mtf2_condition] = request.security(syminfo.tickerid, tf_2, get_mrc())
//************************************************************************************************************
// Drawing Start {
//************************************************************************************************************
float p_meanline = drawchannel ? meanline : na
float p_upband1 = drawchannel ? upband1 : na
float p_loband1 = drawchannel ? loband1 : na
float p_upband2 = drawchannel ? upband2 : na
float p_loband2 = drawchannel ? loband2 : na
z = plot(p_meanline, color=color.new(#FFCD00, 0), style=plot.style_line, title=' Mean', linewidth=2)
x1 = plot(p_upband1, color=color.new(color.green, 50), style=plot.style_circles, title=' R1', linewidth=1)
x2 = plot(p_loband1, color=color.new(color.green, 50), style=plot.style_circles, title=' S1', linewidth=1)
y1 = plot(p_upband2, color=color.new(color.red, 50), style=plot.style_line, title=' R2', linewidth=1)
y2 = plot(p_loband2, color=color.new(color.red, 50), style=plot.style_line, title=' S2', linewidth=1)
//-----------------------
// Draw zone
//-----------------------
//---
var color1 = #FF0000
var color2 = #FF4200
var color3 = #FF5D00
var color4 = #FF7400
var color5 = #FF9700
var color6 = #FFAE00
var color7 = #FFC500
var color8 = #FFCD00
//---
float upband2_1 = drawchannel and displayzone ? upband2 + meanrange * gradsize * 4 : na
float loband2_1 = drawchannel and displayzone ? loband2 - meanrange * gradsize * 4 : na
float upband2_2 = drawchannel and displayzone ? upband2 + meanrange * gradsize * 3 : na
float loband2_2 = drawchannel and displayzone ? loband2 - meanrange * gradsize * 3 : na
float upband2_3 = drawchannel and displayzone ? upband2 + meanrange * gradsize * 2 : na
float loband2_3 = drawchannel and displayzone ? loband2 - meanrange * gradsize * 2 : na
float upband2_4 = drawchannel and displayzone ? upband2 + meanrange * gradsize * 1 : na
float loband2_4 = drawchannel and displayzone ? loband2 - meanrange * gradsize * 1 : na
float upband2_5 = drawchannel and displayzone ? upband2 + meanrange * gradsize * 0 : na
float loband2_5 = drawchannel and displayzone ? loband2 - meanrange * gradsize * 0 : na
float upband2_6 = drawchannel and displayzone ? upband2 + meanrange * gradsize * -1 : na
float loband2_6 = drawchannel and displayzone ? loband2 - meanrange * gradsize * -1 : na
float upband2_7 = drawchannel and displayzone ? upband2 + meanrange * gradsize * -2 : na
float loband2_7 = drawchannel and displayzone ? loband2 - meanrange * gradsize * -2 : na
float upband2_8 = drawchannel and displayzone ? upband2 + meanrange * gradsize * -3 : na
float loband2_8 = drawchannel and displayzone ? loband2 - meanrange * gradsize * -3 : na
float upband2_9 = drawchannel and displayzone ? upband2 + meanrange * gradsize * -4 : na
float loband2_9 = drawchannel and displayzone ? loband2 - meanrange * gradsize * -4 : na
//---
plot_upband2_1 = plot(upband2_1, color=na, display=display.none, transp=100)
plot_loband2_1 = plot(loband2_1, color=na, display=display.none, transp=100)
plot_upband2_2 = plot(upband2_2, color=na, display=display.none, transp=100)
plot_loband2_2 = plot(loband2_2, color=na, display=display.none, transp=100)
plot_upband2_3 = plot(upband2_3, color=na, display=display.none, transp=100)
plot_loband2_3 = plot(loband2_3, color=na, display=display.none, transp=100)
plot_upband2_4 = plot(upband2_4, color=na, display=display.none, transp=100)
plot_loband2_4 = plot(loband2_4, color=na, display=display.none, transp=100)
plot_upband2_5 = plot(upband2_5, color=na, display=display.none, transp=100)
plot_loband2_5 = plot(loband2_5, color=na, display=display.none, transp=100)
plot_upband2_6 = plot(upband2_6, color=na, display=display.none, transp=100)
plot_loband2_6 = plot(loband2_6, color=na, display=display.none, transp=100)
plot_upband2_7 = plot(upband2_7, color=na, display=display.none, transp=100)
plot_loband2_7 = plot(loband2_7, color=na, display=display.none, transp=100)
plot_upband2_8 = plot(upband2_8, color=na, display=display.none, transp=100)
plot_loband2_8 = plot(loband2_8, color=na, display=display.none, transp=100)
plot_upband2_9 = plot(upband2_9, color=na, display=display.none, transp=100)
plot_loband2_9 = plot(loband2_9, color=na, display=display.none, transp=100)
//---
fill(plot_upband2_1, plot_upband2_2, color=color1, transp=gradtransp)
fill(plot_loband2_1, plot_loband2_2, color=color1, transp=gradtransp)
fill(plot_upband2_2, plot_upband2_3, color=color2, transp=gradtransp)
fill(plot_loband2_2, plot_loband2_3, color=color2, transp=gradtransp)
fill(plot_upband2_3, plot_upband2_4, color=color3, transp=gradtransp)
fill(plot_loband2_3, plot_loband2_4, color=color3, transp=gradtransp)
fill(plot_upband2_4, plot_upband2_5, color=color4, transp=gradtransp)
fill(plot_loband2_4, plot_loband2_5, color=color4, transp=gradtransp)
fill(plot_upband2_5, plot_upband2_6, color=color5, transp=gradtransp)
fill(plot_loband2_5, plot_loband2_6, color=color5, transp=gradtransp)
fill(plot_upband2_6, plot_upband2_7, color=color6, transp=gradtransp)
fill(plot_loband2_6, plot_loband2_7, color=color6, transp=gradtransp)
fill(plot_upband2_7, plot_upband2_8, color=color7, transp=gradtransp)
fill(plot_loband2_7, plot_loband2_8, color=color7, transp=gradtransp)
fill(plot_upband2_8, plot_upband2_9, color=color8, transp=gradtransp)
fill(plot_loband2_8, plot_loband2_9, color=color8, transp=gradtransp)
//-----------------------
// Plot Extension
//-----------------------
if displayline and enable_mtf and mtf_disp_typ == 'Always Display'
displayline := false
displayline
var line mean = na
line.delete(mean)
mean := displayline ? f_PriceLine(meanline, #FFCD00) : na
var line res1 = na
line.delete(res1)
res1 := displayline ? f_PriceLine(upband1, color.green) : na
var line sup1 = na
line.delete(sup1)
sup1 := displayline ? f_PriceLine(loband1, color.green) : na
var line res2 = na
line.delete(res2)
res2 := displayline ? f_PriceLine(upband2, color.red) : na
var line sup2 = na
line.delete(sup2)
sup2 := displayline ? f_PriceLine(loband2, color.red) : na
//--------------
// Prep MTF Label
//--------------
var brl = '\n--------------------------------------'
dist_0 = 'Distance from Mean: ' + str.tostring((close - meanline) / close * 100, '#.##') + ' %'
dist_1 = 'Distance from Mean: ' + str.tostring((close - mtf1_meanline) / close * 100, '#.##') + ' %'
dist_2 = 'Distance from Mean: ' + str.tostring((close - mtf2_meanline) / close * 100, '#.##') + ' %'
var title = 'Mean Reversion Channel\nMultiple TimeFrame Analysis' + brl
tf0 = '\n\nTimeframe: ' + tf_0 + ' (Current)\n\nStatus: ' + get_stat(condition) + dist_0 + brl
tf1 = not timeframe.ismonthly ? '\n\nTimeframe: ' + tf_1 + '\n\nStatus: ' + get_stat(mtf1_condition) + dist_1 + brl : ''
tf2 = not timeframe.isweekly and not timeframe.ismonthly ? '\n\nTimeframe: ' + tf_2 + '\n\nStatus: ' + get_stat(mtf2_condition) + dist_2 + brl : ''
mtf_lbl = title + tf0 + tf1 + tf2
var label label_mtf = na
label.delete(label_mtf)
label_mtf := enable_mtf ? f_MTFLabel(mtf_lbl, meanline) : na
//} Drawing End
Show all 450 lines