MACD MTF Lines
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © danilogalisteu

//@version=5
indicator("MACD MTF Lines")

// Price source
price = input.source(close, "Price source", group="Price source")

// MACD parameters
group_macd = "MACD parameters"
pfast = input.int(1, "Fast Period", minval=1, group=group_macd)
pslow = input.int(33, "Slow Period", minval=1, group=group_macd)
psign = input.int(3, "Signal Period", minval=1, group=group_macd)
tmain = input.string("EMA", "Type of MA for main line", options=["EMA", "SMA"], group=group_macd)
tsign = input.string("EMA", "Type of MA for signal line", options=["EMA", "SMA"], group=group_macd)

// Timeframe selection
group_tf = "Timeframe selection and score weights"
tf1  = input.timeframe(  "1", "TF 1", group=group_tf, inline="tf1")
en1  = input.bool(true, "Show", group=group_tf, inline="tf1")
sc1  = input.float(1.0, "Weight", group=group_tf, inline="tf1")
tf2  = input.timeframe(  "3", "TF 2", group=group_tf, inline="tf2")
en2  = input.bool(true, "Show", group=group_tf, inline="tf2")
sc2  = input.float(1.0, "Weight", group=group_tf, inline="tf2")
tf3  = input.timeframe(  "5", "TF 3", group=group_tf, inline="tf3")
en3  = input.bool(true, "Show", group=group_tf, inline="tf3")
sc3  = input.float(1.0, "Weight", group=group_tf, inline="tf3")
tf4  = input.timeframe( "15", "TF 4", group=group_tf, inline="tf4")
en4  = input.bool(true, "Show", group=group_tf, inline="tf4")
sc4  = input.float(1.0, "Weight", group=group_tf, inline="tf4")
tf5  = input.timeframe( "30", "TF 5", group=group_tf, inline="tf5")
en5  = input.bool(true, "Show", group=group_tf, inline="tf5")
sc5  = input.float(1.0, "Weight", group=group_tf, inline="tf5")
tf6  = input.timeframe( "45", "TF 6", group=group_tf, inline="tf6")
en6  = input.bool(true, "Show", group=group_tf, inline="tf6")
sc6  = input.float(1.0, "Weight", group=group_tf, inline="tf6")
tf7  = input.timeframe( "60", "TF 7", group=group_tf, inline="tf7")
en7  = input.bool(true, "Show", group=group_tf, inline="tf7")
sc7  = input.float(1.0, "Weight", group=group_tf, inline="tf7")
tf8  = input.timeframe("120", "TF 8", group=group_tf, inline="tf8")
en8  = input.bool(true, "Show", group=group_tf, inline="tf8")
sc8  = input.float(1.0, "Weight", group=group_tf, inline="tf8")
tf9  = input.timeframe("180", "TF 9", group=group_tf, inline="tf9")
en9  = input.bool(true, "Show", group=group_tf, inline="tf9")
sc9  = input.float(1.0, "Weight", group=group_tf, inline="tf9")
tf10 = input.timeframe("240", "TF 10", group=group_tf, inline="tf10")
en10 = input.bool(true, "Show", group=group_tf, inline="tf10")
sc10 = input.float(1.0, "Weight", group=group_tf, inline="tf10")
tf11 = input.timeframe( "1D", "TF 11", group=group_tf, inline="tf11")
en11 = input.bool(true, "Show", group=group_tf, inline="tf11")
sc11 = input.float(1.0, "Weight", group=group_tf, inline="tf11")
tf12 = input.timeframe( "1W", "TF 12", group=group_tf, inline="tf12")
en12 = input.bool(true, "Show", group=group_tf, inline="tf12")
sc12 = input.float(1.0, "Weight", group=group_tf, inline="tf12")
tf13 = input.timeframe( "1M", "TF 13", group=group_tf, inline="tf13")
en13 = input.bool(true, "Show", group=group_tf, inline="tf13")
sc13 = input.float(1.0, "Weight", group=group_tf, inline="tf13")

// Trend detection
group_trend = "Trend detection"
show_bg   = input.bool(false, "Show trend color in background", group=group_trend)
enable_trend_alert = input.bool(false, "Enable alert on trend change", group=group_trend)
score_threshold_trend = input.float(7, "Minimum score for trend change", minval=0, group=group_trend)
enable_extra_alert = input.bool(false, "Enable extra alert", group=group_trend)
score_threshold_alert = input.float(7, "Minimum score for extra alert", minval=0, group=group_trend)

// MTF helpers
ma_mtf(ts, period, tf, matype) =>
    expr = matype == "EMA" ? ta.ema(ts, period) : math.sum(ts, period) - ts[period-1]
    prev = request.security(syminfo.tickerid, tf, expr[1], barmerge.gaps_off, barmerge.lookahead_on)
    period > 1 ? matype == "EMA" ? (2 * ts + (1 + period - 2) * prev) / (1 + period) : (ts + prev) / period : ts

macd_sign_mtf(ts, fast, slow, signal, tf, main_func, sign_func) =>
    m1 = ma_mtf(ts, fast, tf, main_func) - ma_mtf(ts, slow, tf, main_func)
    m2 = ma_mtf(m1, signal, tf, sign_func)
    math.sign(m1 - m2)

// Signals
v1  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf1, tmain, tsign))
v2  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf2, tmain, tsign))
v3  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf3, tmain, tsign))
v4  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf4, tmain, tsign))
v5  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf5, tmain, tsign))
v6  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf6, tmain, tsign))
v7  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf7, tmain, tsign))
v8  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf8, tmain, tsign))
v9  = nz(macd_sign_mtf(price, pfast, pslow, psign, tf9, tmain, tsign))
v10 = nz(macd_sign_mtf(price, pfast, pslow, psign, tf10, tmain, tsign))
v11 = nz(macd_sign_mtf(price, pfast, pslow, psign, tf11, tmain, tsign))
v12 = nz(macd_sign_mtf(price, pfast, pslow, psign, tf12, tmain, tsign))
v13 = nz(macd_sign_mtf(price, pfast, pslow, psign, tf13, tmain, tsign))

score = v1 * sc1 + v2 * sc2 + v3 * sc3 + v4 * sc4 + v5 * sc5 + v6 * sc6 + v7 * sc7 + v8 * sc8 + v9 * sc9 + v10 * sc10 + v11 * sc11 + v12 * sc12 + v13 * sc13
score_max = math.abs(sc1) + math.abs(sc2) + math.abs(sc3) + math.abs(sc4) + math.abs(sc5) + math.abs(sc6) + math.abs(sc7) + math.abs(sc8) + math.abs(sc9) + math.abs(sc10) + math.abs(sc11) + math.abs(sc12) + math.abs(sc13)

// State changes
state_trend = 0
state_trend := nz(state_trend[1])
if state_trend[1] < 1 and score >= score_threshold_trend
    state_trend := 1
if state_trend[1] > -1 and score <= -score_threshold_trend
    state_trend := -1

state_alert = 0
state_alert := nz(state_alert[1])
if state_alert[1] < 1 and score >= score_threshold_alert
    state_alert := 1
if state_alert[1] > -1 and score <= -score_threshold_alert
    state_alert := -1

// Alerts
s_dir_trend = state_trend == 1 ? 'long' : state_trend == -1 ? 'short' : "neutral"
alert_msg_trend = str.format("MACD-MTF({0}, {1}, {2}) Trend Change\nsignal {3}\nticker {4}\ntimeframe {5}\nprice {6}", pfast, pslow, psign, s_dir_trend, syminfo.tickerid, timeframe.period, close)
if enable_trend_alert and state_trend != state_trend[1]
    alert(alert_msg_trend, alert.freq_once_per_bar_close)


s_dir_alert = state_alert == 1 ? 'long' : state_alert == -1 ? 'short' : "neutral"
alert_msg_alert = str.format("MACD-MTF({0}, {1}, {2}) Trend Alert\nsignal {3}\nticker {4}\ntimeframe {5}\nprice {6}", pfast, pslow, psign, s_dir_alert, syminfo.tickerid, timeframe.period, close)
if enable_extra_alert and state_alert != state_alert[1]
    alert(alert_msg_alert, alert.freq_once_per_bar_close)

// Colors
lw = 3
color_pos = color.green
color_neg = color.red
color_neu = color.yellow
color_pos_bg = color.rgb(76, 175, 79, 85)
color_neg_bg = color.rgb(255, 82, 82, 85)
color_neu_bg = color.rgb(255, 235, 59, 85)

// Enable or disable specific lines
plot_level = 0.0
plot_level  := en1  ? plot_level - 10.0 : plot_level
plot_level1  = en1  ? plot_level : na
plot_level  := en2  ? plot_level - 10.0 : plot_level
plot_level2  = en2  ? plot_level : na
plot_level  := en3  ? plot_level - 10.0 : plot_level
plot_level3  = en3  ? plot_level : na
plot_level  := en4  ? plot_level - 10.0 : plot_level
plot_level4  = en4  ? plot_level : na
plot_level  := en5  ? plot_level - 10.0 : plot_level
plot_level5  = en5  ? plot_level : na
plot_level  := en6  ? plot_level - 10.0 : plot_level
plot_level6  = en6  ? plot_level : na
plot_level  := en7  ? plot_level - 10.0 : plot_level
plot_level7  = en7  ? plot_level : na
plot_level  := en8  ? plot_level - 10.0 : plot_level
plot_level8  = en8  ? plot_level : na
plot_level  := en9  ? plot_level - 10.0 : plot_level
plot_level9  = en9  ? plot_level : na
plot_level  := en10 ? plot_level - 10.0 : plot_level
plot_level10 = en10 ? plot_level : na
plot_level  := en11 ? plot_level - 10.0 : plot_level
plot_level11 = en11 ? plot_level : na
plot_level  := en12 ? plot_level - 10.0 : plot_level
plot_level12 = en12 ? plot_level : na
plot_level  := en13 ? plot_level - 10.0 : plot_level
plot_level13 = en13 ? plot_level : na

// Plotting
plot(plot_level1, "TF1", color=v1 > 0 ? color_pos : v1 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level2, "TF2", color=v2 > 0 ? color_pos : v2 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level3, "TF3", color=v3 > 0 ? color_pos : v3 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level4, "TF4", color=v4 > 0 ? color_pos : v4 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level5, "TF5", color=v5 > 0 ? color_pos : v5 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level6, "TF6", color=v6 > 0 ? color_pos : v6 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level7, "TF7", color=v7 > 0 ? color_pos : v7 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level8, "TF8", color=v8 > 0 ? color_pos : v8 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level9, "TF9", color=v9 > 0 ? color_pos : v9 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level10, "TF10", color=v10 > 0 ? color_pos : v10 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level11, "TF11", color=v11 > 0 ? color_pos : v11 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level12, "TF12", color=v12 > 0 ? color_pos : v12 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)
plot(plot_level13, "TF13", color=v13 > 0 ? color_pos : v13 < 0 ? color_neg : color_neu, linewidth=lw, display=display.pane)

// Trend/alert score and state
color_trend = state_trend > 0 ? color_pos : state_trend < 0 ? color_neg : color_neu
color_alert = state_alert > 0 ? color_pos : state_alert < 0 ? color_neg : color_neu
plot(score, "Score", color=color_trend, display=display.data_window)
plot(score_max, "Maximum score (+/-)", color=color_neu, display=display.data_window)
plot(state_trend, "Trend", color=color_trend, display=display.data_window)
plot(state_alert, "Alert", color=color_alert, display=display.data_window)

// Background
color_bg = state_trend > 0 ? color_pos_bg : state_trend < 0 ? color_neg_bg : color_neu_bg
bgcolor(show_bg ? color_bg : na)
