MACD Objective Breakouts + Alerts
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © JustDamion
// Last Updated: 9/6/2022

//@version=5
indicator(title="MACD Objective Breakouts + Alerts", shorttitle="MOB Alerts", overlay=true)

// MACD Settings
var g_MACD      = "MACD Settings"
checkRDiv       = input.bool(title="Regular Divergence Detection", defval=true, group=g_MACD, tooltip="When true this will check for regular bullish and bearish divergence based on the levels detected by the indicator.")
checkHDiv       = input.bool(title="Hidden Divergence Detection", defval=false, group=g_MACD, tooltip="When true this will check for hidden bullish and bearish divergence based on the levels detected by the indicator.")
minHistBars     = input.int(title="Minimum Histogram Bars", defval=1, minval=1, group=g_MACD, tooltip="The histogram will need to stay one color for this many bars to determine a new low or high.")
fast            = input.int(title="Fast Length", defval=12, minval=1, group=g_MACD, tooltip="Value used to calculate MACD line.")
slow            = input.int(title="Slow Length", defval=26, minval=1, group=g_MACD, tooltip="Value used to calculate MACD line.")
signalLength    = input.int(title="Signal Length", defval=9, minval=1, group=g_MACD, tooltip="This smooths the signal line, higher values = less volitility.")
macdLine        = input.string(title="MACD MA Type", options=['SMA', 'EMA', 'WMA', 'VWMA'], defval='EMA', group=g_MACD, tooltip="Type of moving average used to calculate MACD line.")
signalLine      = input.string(title="Signal MA Type", options=['SMA', 'EMA', 'WMA', 'VWMA'], defval='EMA', group=g_MACD, tooltip="Type of moving average used to calculate signal line.")
source          = input.source(title="Source", defval=close, group=g_MACD, tooltip="Source used to generate MACD.")
// Alert Settings
var g_alerts    = "Alert Settings"
a_closeCheck    = input.bool(title="Only On Close Outside of Level", defval=true, group=g_alerts, tooltip="When checked, you will receive an alert when a bar closes outside of a key level.\n\nWhen unchecked, you will receive an alert when price touches a key level.")
a_firstCheck    = input.bool(title="Only First Breakout Candle", defval=true, group=g_alerts, tooltip="Wheck checked, you will only receive an alert on the first bar that activates an alert until new levels are formed.\n\nWhen unchecked, you will receive an alert everytime a bar activates an alert.")
a_divCheck      = input.bool(title="Only Divergence Breakouts", defval=false, group=g_alerts, tooltip="When checked, you will only receive an alert after divergence has been detected.")
a_longCheck     = input.bool(title="Long Breakouts", defval=false, group=g_alerts, tooltip="Will generate alert when candle closes above upper line.")
a_longMsg       = input.string(title="    Alert Message", defval="MOB: Long Breakout!", group=g_alerts, tooltip="Message that will be sent when long breakout occurs.")
a_shortCheck    = input.bool(title="Short Breakouts", defval=false, group=g_alerts, tooltip="Will generate alert when candle closes below lower line.")
a_shortMsg      = input.string(title="    Alert Message", defval="MOB: Short Breakout!", group=g_alerts, tooltip="Message that will be sent when short breakout occurs.")
// Style Settings
var g_style     = "Style Settings"
shapeCheck      = input.bool(title="Plot Shapes On Breakouts", defval=true, group=g_style, tooltip="Plots shapes to indicate a close above or below key levels.")
lineCheck       = input.bool(title="Plot Lines at Key Levels", defval=true, group=g_style, tooltip="Plots horizontal lines at the current key levels.")
highColor       = input.color(title="Upper Line", defval=color.green, group=g_style, tooltip="Sets the color of the upper line.")
lowColor        = input.color(title="Lower Line", defval=color.red, group=g_style, tooltip="Sets the color of the lower line.")
lineWidth       = input.int(title="Line Width", minval=1, defval=1, group=g_style, tooltip="Changes the width of the lines.")

// Calculate MACD line
fastMA = ta.ema(source, fast)
slowMA = ta.ema(source, slow)

switch macdLine
    "WMA" =>
        fastMA := ta.wma(source, fast)
        slowMA := ta.wma(source, slow)
    "SMA" =>
        fastMA := ta.sma(source, fast)
        slowMA := ta.sma(source, slow)
    "EMA" =>
        fastMA := ta.ema(source, fast)
        slowMA := ta.ema(source, slow)
    "VWMA" =>
        fastMA := ta.vwma(source, fast)
        slowMA := ta.vwma(source, slow)
macd = fastMA - slowMA

// Calculate signal line
signal = ta.ema(macd, signalLength)
switch signalLine
    "WMA" => signal := ta.wma(macd, signalLength)
    "SMA" => signal := ta.sma(macd, signalLength)
    "EMA" => signal := ta.ema(macd, signalLength)
    "VWMA" => signal := ta.vwma(macd, signalLength)

// Set variables for key levels
var arrayHigh = array.new_float(0)
var arrayLow = array.new_float(0)
var histValues = array.new_float(0)
array.push(arrayHigh, na)
array.push(arrayLow, na)
array.push(histValues, na)
var line lineHigh = line.new(x1=bar_index[1], y1=na, x2=bar_index, y2=na, color=lineCheck ? highColor : na, width=lineWidth, extend=extend.both)
var line lineLow = line.new(x1=bar_index[1], y1=na, x2=bar_index, y2=na, color=lineCheck ? lowColor : na, width=lineWidth, extend=extend.both)
var float priceHigh = na
var float lastPriceHigh = na
var float priceLow = na
var float lastPriceLow = na
var float histHigh = na
var float lastHistHigh = na
var float histLow = na
var float lastHistLow = na
var bool bullDiv = na
var bool bearDiv = na
var bool hBullDiv = na
var bool hBearDiv = na
var barCount = 0

// Push values to arrays
if macd > signal and barstate.isconfirmed
    array.push(arrayHigh, high)
    array.push(histValues, macd - signal)
if macd < signal and barstate.isconfirmed
    array.push(arrayLow, low)
    array.push(histValues, macd - signal)

// Detect new key levels
if ta.crossunder(macd, signal) and array.size(histValues)-1 > minHistBars
    lastPriceHigh := priceHigh
    lastHistHigh := histHigh
    priceHigh := array.max(arrayHigh)
    histHigh := array.max(histValues)
    array.clear(arrayHigh)
    array.clear(histValues)
    line.set_y1(lineHigh, priceHigh)
    line.set_y2(lineHigh, priceHigh)
    bearDiv := priceHigh > lastPriceHigh and histHigh < lastHistHigh and checkRDiv
    hBearDiv := priceHigh < lastPriceHigh and histHigh > lastHistHigh and checkHDiv
    barCount := 0
if ta.crossover(macd, signal) and array.size(histValues)-1 > minHistBars
    lastPriceLow := priceLow
    lastHistLow := histLow
    priceLow := array.min(arrayLow)
    histLow := array.min(histValues)
    array.clear(arrayLow)
    array.clear(histValues)
    line.set_y1(lineLow, priceLow)
    line.set_y2(lineLow, priceLow)
    bullDiv := priceLow < lastPriceLow and histLow > lastHistLow and checkRDiv
    hBullDiv := priceLow > lastPriceLow and histLow < lastHistLow and checkHDiv
    barCount := 0

// Count breakout bars
if (close >= priceHigh or close <= priceLow) and barstate.isconfirmed
    barCount += 1

// Plot shapes to indicate breakouts
plotshape(close >= priceHigh and barstate.isconfirmed and shapeCheck and barCount==1, title="First Long Breakout", style=shape.diamond, location=location.belowbar, color=bullDiv or hBullDiv ? color.white : color.green)
plotshape(close <= priceLow and barstate.isconfirmed and shapeCheck and barCount==1, title="First Short Breakout", style=shape.diamond, location=location.abovebar, color=bearDiv or hBearDiv ? color.white : color.red)
plotshape(close >= priceHigh and barstate.isconfirmed and shapeCheck and barCount>1, title="Long Breakout", style=shape.triangleup, location=location.belowbar, color=color.green)
plotshape(close <= priceLow and barstate.isconfirmed and shapeCheck and barCount>1, title="Short Breakout", style=shape.triangledown, location=location.abovebar, color=color.red)

// Generate long alerts
if close >= priceHigh and (a_closeCheck and barstate.isconfirmed) and a_longCheck and (barCount == 1 or a_firstCheck==false) and (bullDiv or a_divCheck==false) and (hBullDiv or a_divCheck==false)
    alert(a_longMsg, alert.freq_once_per_bar_close)
// Generate short alerts
if close <= priceLow and (a_closeCheck and barstate.isconfirmed) and a_shortCheck and (barCount == 1 or a_firstCheck==false) and (bearDiv or a_divCheck==false) and (hBearDiv or a_divCheck==false)
    alert(a_shortMsg, alert.freq_once_per_bar_close)