MTF MA Reference Levels - SamX
// This source code provided free and open-source as defined by the terms of the Mozilla Public License 2.0 (https://mozilla.org/MPL/2.0/)
// with the following additional requirements:
//
// 1. Citations for sources and references must be maintained and updated when appropriate
// 2. Links to strategy references included in indicator tooptips and/or alerts should be retained to give credit to the original source
//             while also providing a freely available source of information on proper use and interpretation
//
// Author:  SamAccountX
//
// The intent of this indicator is to provide a way to easily reference the same moving average across multiple timeframes without the clutter
// of multiple moving averate lines being printed on the chart (which can also lead to potential difficulty in easily distinguishing which line
// represent what timeframe).
//
// Note:  While I usually prefer to structure my code by a more "standard" flow of Inputs -> Function Definitiona -> Functional Code,
//        I opted to take a slightly different approach in this indicator to break out the individual moving average timeframe and color
//        inputs along with their actual calculations and plot code, as this makes scaling out (adding additional timeframes) a lot easier.
//
//@version=5
indicator(title="MTF MA Reference Levels - SamX", shorttitle="MTF MAs", overlay=true)
//
//          BEGIN inputs         
//
// Global settings
g_Global = "Global Settings"
maType = input.string(title="Moving Average Calculation", defval="Exponential", group=g_Global, tooltip='Select the moving average calculation method to use.  This ' +
                 'same calculation method will be used for ALL selected timeframes!', 
                  options=["Simple", "Exponential", "Weighted", "Volume-Weigehted", "Smoothed", "RMA", "Linear", "Hull", "ALMA", "Double EMA", "Triple EMA"])
maLength = input.int(title="Moving Average Length", defval=144, minval=2, maxval=500, step=1, group=g_Global, tooltip='Select the moving average length to use.  This ' +
                 'same length will be used for ALL selected timeframes!')
maSource = input.source(title="Moving Average Price Source", defval=close, group=g_Global, tooltip='Select the moving average price source to use.  This ' +
                 'same price source will be used for ALL selected timeframes!')
allowRepainting = input.bool(title="Allow Real-Time Updates", defval=true, group=g_Global, tooltip='If selected (enabled), moving average calculations on the current bar ' +
                 'will update as new price data comes in, which may alter the line level and/or displayed Moving Average value (also known as "repainting").  This ' +
                 'same setting will be used for ALL selected timeframes!')
lineOffset = input.int(title="Line offset", group=g_Global, defval=20, minval=0, maxval=100, step=1, tooltip='This setting controls the left offest (number candles to the ' +
                 'left of the current/latest candle) for the MA lines.  A value of 0 will start the line on the current candle.')
showLabel = input.bool(title='Show MA Line Labels', defval=true, group=g_Global, tooltip='If selected, labels will be placed on the MA lines.  \n\n' +
                 'The label position/direction will be based on the current MA value compared to the value on the previous candle.  If the current MA value is greater than the ' +
                 'previous one, it will be a down-label (printed above the MA line with the carat pointing down), and as an up-label if the current MA vale us greater than the ' +
                 'previous one')
labelOffset = input.int(title="Label offset", group=g_Global, defval=40, minval=0, maxval=100, step=1, tooltip='This setting controls the right offest (number candles to the ' +
                 'right of the current/latest candle) for labels.  A value of 0 will print the label on the current candle.')
additionalabelOffset = input.int(title="Additional Label offset", group=g_Global, defval=10, minval=0, maxval=100, step=1, tooltip='This setting controls an additional right offest for ' +
                 'subesequent labels (number candles to the right of the previous TF label).  A value of 0 will print the labels in the same location on the X (time) axis, which might result ' +
                 'in label overlap.')
labelAsPeriod = input.bool(title='Show TF in Label', defval=false, group=g_Global, tooltip='The default label text is the actual current MA value rounded to the nearest tick of the ' +
                 'current chart.  If this option is selected, the label text will instead be replaced with the timeframe reference period String representation. \n\n' +
                 'See: https://www.tradingview.com/pine-script-docs/en/v5/concepts/Timeframes.html#timeframe-string-specifications')
//          END inputs         



//          BEGIN Function Definitions          
//
// DEMA
dema(src, len) =>
    de1 = ta.ema(src, len)
    de2 = ta.ema(de1, len)
    dema = 2 * de1 - de2

// TEMA
tema(src, len) =>
    te1 = ta.ema(src, len)
    te2 = ta.ema(te1, len)
    te3 = ta.ema(te2, len)
    tema = 3 * (te1 - te2) + te3

// Function to calculate smoothed moving average...
smoothedMovingAvg(src, len) => 
	smma = 0.0
	// TV will complain about the use of the ta.sma function use inside a function saying that it should be called on each calculation, 
	// but since we're only using it once to set the initial value for the smoothed MA (when the previous smma value is NaN - Not a Number)
	// and using the previous smma value for each subsequent iteration, this can be safely ignored
	smma := na(smma[1]) ? ta.sma(src, len) : (smma[1] * (len - 1) + src) / len 
	smma
	
// MA calculation
ma(source, length, type) =>
    switch type
        "Simple" => ta.sma(source, length)
        "Exponential" => ta.ema(source, length)
        "Weighted" => ta.wma(source, length)
        "Volume-Weigehted" => ta.vwma(source, length)
        "Smoothed" => smoothedMovingAvg(source, length)
        "RMA" => ta.rma(source, length)
        "Linear" => ta.linreg(source, length, 0)
        "Hull" => ta.hma(source, length)
        "ALMA" => ta.alma(source, length, 0.85, 6)
        "Double EMA" => dema(source, length)
        "Triple EMA" => tema(source, length)
        
//
//          END Function Definitions          
//
//
//          Functional code start       
//
// TODO:  Get EMA values for up to 5 higher timeframes and plot short-lenght lines extending fully to the right,
//        with a label indicating the MA timeframe and (optionally) MA value.
//
//        Bonus points: ability to have these printed in an on-chart table...
//
// Explicitly define our ticker to help ensure that we're always getting ACTUAL price instead of relying on the input
// ticker info and input vars (as they tend to inherit the type from what's displayed on the current chart)
realPriceTicker = ticker.new(prefix=syminfo.prefix, ticker=syminfo.ticker)

// Tracking var to assist in scale/offset of labels
labelPrintCounter = 0

//         BEGIN MTF Code sections       
// 
// TF #1 (intended as chart)
//
g_tf1 = 'Timeframe #1'
showTF1 = input.bool(title='', defval=true, group=g_tf1, inline='t1_1')
tf1Timeframe = input.timeframe(title='Timeframe:', defval='', group=g_tf1, inline='t1_1')
tf1LineColor = input.color(title='Line/Label:', defval=color.rgb(255, 255, 255, 0), group=g_tf1, inline='t1_2')
tf1LabelTextColor = input.color(title='      Label Text:', defval=color.rgb(0, 0, 0, 0), group=g_tf1, inline='t1_2')

tf1MA = request.security(symbol=realPriceTicker, timeframe=tf1Timeframe, expression=ma(maSource, maLength, maType), lookahead=barmerge.lookahead_off, gaps=barmerge.gaps_off)
if (showTF1)
	tf1MALine = line.new(x1=bar_index[lineOffset], x2=bar_index + 1, y1=allowRepainting ? tf1MA : tf1MA[1], y2=allowRepainting ? tf1MA : tf1MA[1], extend=extend.right, style=line.style_dashed, color=tf1LineColor)
	line.delete(tf1MALine[1])
	if (showLabel)
		labelText = labelAsPeriod ? 
			 str.tostring(str.length(tf1Timeframe) == 0 ? timeframe.period : tf1Timeframe) : 
			 allowRepainting ? 
				 str.tostring(tf1MA, format.mintick) :
				 str.tostring(tf1MA[1], format.mintick)
		labelStyleUp = allowRepainting ? 
			 tf1MA > tf1MA[1] ?
				 true :
				 false :
			 tf1MA[1] > tf1MA[2] ?
				 true :
				 false
		tlLabelOffset = (additionalabelOffset * labelPrintCounter) + labelOffset
		tf1MALabel = label.new(x=(bar_index + tlLabelOffset), y=allowRepainting ? tf1MA : tf1MA[1], style=labelStyleUp ? label.style_label_up : label.style_label_down, text=labelText, color=tf1LineColor, textcolor=tf1LabelTextColor)
		label.delete(tf1MALabel[1])
		labelPrintCounter := labelPrintCounter + 1

// TF #2 (intended as 1 TF higher)
//
g_tf2 = 'Timeframe #2'
showTF2 = input.bool(title='', defval=true, group=g_tf2, inline='t1_1')
tf2Timeframe = input.timeframe(title='Timeframe:', defval='5', group=g_tf2, inline='t1_1')
tf2LineColor = input.color(title='Line/Label:', defval=color.rgb(255, 255, 0, 0), group=g_tf2, inline='t1_2')
tf2LabelTextColor = input.color(title='      Label Text:', defval=color.rgb(0, 0, 0, 0), group=g_tf2, inline='t1_2')

tf2MA = request.security(symbol=realPriceTicker, timeframe=tf2Timeframe, expression=ma(maSource, maLength, maType), lookahead=barmerge.lookahead_off, gaps=barmerge.gaps_off)
if (showTF2)
	tf2MALine = line.new(x1=bar_index[lineOffset], x2=bar_index + 1, y1=allowRepainting ? tf2MA : tf2MA[1], y2=allowRepainting ? tf2MA : tf2MA[1], extend=extend.right, style=line.style_dashed, color=tf2LineColor)
	line.delete(tf2MALine[1])
	if (showLabel)
		labelText = labelAsPeriod ? 
			 str.tostring(str.length(tf2Timeframe) == 0 ? timeframe.period : tf2Timeframe) : 
			 allowRepainting ? 
				 str.tostring(tf2MA, format.mintick) :
				 str.tostring(tf2MA[1], format.mintick)
		labelStyleUp = allowRepainting ? 
			 tf2MA > tf2MA[1] ?
				 true :
				 false :
			 tf2MA[1] > tf2MA[2] ?
				 true :
				 false
		t2LabelOffset = (additionalabelOffset * labelPrintCounter) + labelOffset
		tf2MALabel = label.new(x=(bar_index + t2LabelOffset), y=allowRepainting ? tf2MA : tf2MA[1], style=labelStyleUp ? label.style_label_up : label.style_label_down, text=labelText, color=tf2LineColor, textcolor=tf2LabelTextColor)
		label.delete(tf2MALabel[1])
		labelPrintCounter := labelPrintCounter + 1
    
// TF #3 (intended as 2 TF higher)
//
g_tf3 = 'Timeframe #3'
showTF3 = input.bool(title='', defval=true, group=g_tf3, inline='t1_1')
tf3Timeframe = input.timeframe(title='Timeframe:', defval='15', group=g_tf3, inline='t1_1')
tf3LineColor = input.color(title='Line/Label:', defval=color.rgb(0, 255, 255, 0), group=g_tf3, inline='t1_2')
tf3LabelTextColor = input.color(title='      Label Text:', defval=color.rgb(0, 0, 0, 0), group=g_tf3, inline='t1_2')

tf3MA = request.security(symbol=realPriceTicker, timeframe=tf3Timeframe, expression=ma(maSource, maLength, maType), lookahead=barmerge.lookahead_off, gaps=barmerge.gaps_off)
if (showTF3)
	tf3MALine = line.new(x1=bar_index[lineOffset], x2=bar_index + 1, y1=allowRepainting ? tf3MA : tf3MA[1], y2=allowRepainting ? tf3MA : tf3MA[1], extend=extend.right, style=line.style_dashed, color=tf3LineColor)
	line.delete(tf3MALine[1])
	if (showLabel)
		labelText = labelAsPeriod ? 
			 str.tostring(str.length(tf3Timeframe) == 0 ? timeframe.period : tf3Timeframe) : 
			 allowRepainting ? 
				 str.tostring(tf3MA, format.mintick) :
				 str.tostring(tf3MA[1], format.mintick)
		labelStyleUp = allowRepainting ? 
			 tf3MA > tf3MA[1] ?
				 true :
				 false :
			 tf3MA[1] > tf3MA[2] ?
				 true :
				 false
		t3LabelOffset = (additionalabelOffset * labelPrintCounter) + labelOffset
		tf3MALabel = label.new(x=(bar_index + t3LabelOffset), y=allowRepainting ? tf3MA : tf3MA[1], style=labelStyleUp ? label.style_label_up : label.style_label_down, text=labelText, color=tf3LineColor, textcolor=tf3LabelTextColor)
		label.delete(tf3MALabel[1])
		labelPrintCounter := labelPrintCounter + 1

// TF #4 (intended as 3 TF higher)
//
g_tf4 = 'Timeframe #4'
showTF4 = input.bool(title='', defval=true, group=g_tf4, inline='t1_1')
tf4Timeframe = input.timeframe(title='Timeframe:', defval='60', group=g_tf4, inline='t1_1')
tf4LineColor = input.color(title='Line/Label:', defval=color.rgb(255, 0, 255, 0), group=g_tf4, inline='t1_2')
tf4LabelTextColor = input.color(title='      Label Text:', defval=color.rgb(255, 255, 180, 0), group=g_tf4, inline='t1_2')

tf4MA = request.security(symbol=realPriceTicker, timeframe=tf4Timeframe, expression=ma(maSource, maLength, maType), lookahead=barmerge.lookahead_off, gaps=barmerge.gaps_off)
if(showTF4)
	tf4MALine = line.new(x1=bar_index[lineOffset], x2=bar_index + 1, y1=allowRepainting ? tf4MA : tf4MA[1], y2=allowRepainting ? tf4MA : tf4MA[1], extend=extend.right, style=line.style_dashed, color=tf4LineColor)
	line.delete(tf4MALine[1])
	if (showLabel)
		labelText = labelAsPeriod ? 
			 str.tostring(str.length(tf4Timeframe) == 0 ? timeframe.period : tf4Timeframe) : 
			 allowRepainting ? 
				 str.tostring(tf4MA, format.mintick) :
				 str.tostring(tf4MA[1], format.mintick)
		labelStyleUp = allowRepainting ? 
			 tf4MA > tf4MA[1] ?
				 true :
				 false :
			 tf4MA[1] > tf4MA[2] ?
				 true :
				 false
		t4LabelOffset = (additionalabelOffset * labelPrintCounter) + labelOffset
		tf4MALabel = label.new(x=(bar_index + t4LabelOffset), y=allowRepainting ? tf4MA : tf4MA[1], style=labelStyleUp ? label.style_label_up : label.style_label_down, text=labelText, color=tf4LineColor, textcolor=tf4LabelTextColor)
		label.delete(tf4MALabel[1])
		labelPrintCounter := labelPrintCounter + 1

// TF #5 (intended as 4 TF higher)
//
g_tf5 = 'Timeframe #5'
showTF5 = input.bool(title='', defval=true, group=g_tf5, inline='t1_1')
tf5Timeframe = input.timeframe(title='Timeframe:', defval='120', group=g_tf5, inline='t1_1')
tf5LineColor = input.color(title='Line/Label:', defval=color.rgb(175, 175, 175, 0), group=g_tf5, inline='t1_2')
tf5LabelTextColor = input.color(title='      Label Text:', defval=color.rgb(0, 0, 0, 0), group=g_tf5, inline='t1_2')

tf5MA = request.security(symbol=realPriceTicker, timeframe=tf5Timeframe, expression=ma(maSource, maLength, maType), lookahead=barmerge.lookahead_off, gaps=barmerge.gaps_off)
if(showTF5)
	tf5MALine = line.new(x1=bar_index[lineOffset], x2=bar_index + 1, y1=allowRepainting ? tf5MA : tf5MA[1], y2=allowRepainting ? tf5MA : tf5MA[1], extend=extend.right, style=line.style_dashed, color=tf5LineColor)
	line.delete(tf5MALine[1])
	if (showLabel)
		labelText = labelAsPeriod ? 
			 str.tostring(str.length(tf5Timeframe) == 0 ? timeframe.period : tf5Timeframe) : 
			 allowRepainting ? 
				 str.tostring(tf5MA, format.mintick) :
				 str.tostring(tf5MA[1], format.mintick)
		labelStyleUp = allowRepainting ? 
			 tf5MA > tf5MA[1] ?
				 true :
				 false :
			 tf5MA[1] > tf5MA[2] ?
				 true :
				 false
		t5LabelOffset = (additionalabelOffset * labelPrintCounter) + labelOffset
		tf5MALabel = label.new(x=(bar_index + t5LabelOffset), y=allowRepainting ? tf5MA : tf5MA[1], style=labelStyleUp ? label.style_label_up : label.style_label_down, text=labelText, color=tf5LineColor, textcolor=tf5LabelTextColor)
		label.delete(tf5MALabel[1])
		labelPrintCounter := labelPrintCounter + 1

// TF #6 (intended as 5 TF higher)
//
g_tf6 = 'Timeframe #6'
showTF6 = input.bool(title='', defval=true, group=g_tf6, inline='t1_1')
tf6Timeframe = input.timeframe(title='Timeframe:', defval='240', group=g_tf6, inline='t1_1')
tf6LineColor = input.color(title='Line/Label:', defval=color.rgb(170, 120, 250, 0), group=g_tf6, inline='t1_2')
tf6LabelTextColor = input.color(title='      Label Text:', defval=color.rgb(0, 0, 0, 0), group=g_tf6, inline='t1_2')

tf6MA = request.security(symbol=realPriceTicker, timeframe=tf6Timeframe, expression=ma(maSource, maLength, maType), lookahead=barmerge.lookahead_off, gaps=barmerge.gaps_off)
if (showTF6)
    tf6MALine = line.new(x1=bar_index[lineOffset], x2=bar_index + 1, y1=allowRepainting ? tf6MA : tf6MA[1], y2=allowRepainting ? tf6MA : tf6MA[1], extend=extend.right, style=line.style_dashed, color=tf6LineColor)
    line.delete(tf6MALine[1])
    if (showLabel)
        labelText = labelAsPeriod ? 
             str.tostring(str.length(tf6Timeframe) == 0 ? timeframe.period : tf6Timeframe) : 
             allowRepainting ? 
                 str.tostring(tf6MA, format.mintick) :
                 str.tostring(tf6MA[1], format.mintick)
        labelStyleUp = allowRepainting ? 
             tf6MA > tf6MA[1] ?
                 true :
                 false :
             tf6MA[1] > tf6MA[2] ?
                 true :
                 false
        t6LabelOffset = (additionalabelOffset * labelPrintCounter) + labelOffset
        tf6MALabel = label.new(x=(bar_index + t6LabelOffset), y=allowRepainting ? tf6MA : tf6MA[1], style=labelStyleUp ? label.style_label_up : label.style_label_down, text=labelText, color=tf6LineColor, textcolor=tf6LabelTextColor)
        label.delete(tf6MALabel[1])
    	labelPrintCounter := labelPrintCounter + 1
