Liquidity Levels MTF - Sonarlab
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Sonarlab

//@version=5
indicator("Liquidity Levels - Sonarlab", overlay=true)
_tfSettings = "TimeFrame Settings"
currentTF  = input.bool(true, title = "Liquidity Levels", group=_tfSettings)
htfBool  = input.bool(false, title = "Higher Timeframe",inline="1", group=_tfSettings)
htfTF  = input.timeframe("", title = "", inline="1",group=_tfSettings, tooltip="Display Liquidity Levels for a Higher Timeframe")
// --
_lvlsGrp = "Liquidity Levels"
leftBars  = input.int(15, title = "Left Bars", group=_lvlsGrp, tooltip="Set the lookback point for what determines a Liquidity Level")
rightBars  = input.int(5, title = "Right Bars", group=_lvlsGrp, tooltip="Set the number of bars to confirm a Liquidity Level")
// --
_removeGrp = "Mitigation Settings"
removeMitigated  = input.bool(true, title = "Mitigated", inline="1", group=_removeGrp)
mitiOptions  = input.string("Show", title = "    ",  inline="1", options=["Remove", "Show"], group=_removeGrp, tooltip="Show: Liquidity Levels will stop printing when mitigated and remain on the chart.\nRemove: Liquidity Levels will be removed from the chart when mitigated.")
_candleType  = input.string("Close", title = "Candle type", options=["Close", "Wick"], group=_removeGrp, tooltip="Choose whether a candle close or a candles high/low is needed to determine a mitigated Liquidity Level")
// --
_displayStyleGrp = "Display Styles"
displayStyle  = input.string("Lines", title = "Display Style", options=["Lines", "Boxes"], group=_displayStyleGrp, tooltip="Choose how Liquidity Levels are displayed on the chart")
extentionOptions  = input.string("Current", title = "Extention Options", options=["Short", "Current", "Max"], group=_displayStyleGrp, tooltip="Choose how Liquidity Levels are extended on the chart")
extentionMax = extentionOptions=="Max" ? true : false
extentionCurrent = extentionOptions=="Current" ? true : false
displayLimit  = input.int(5, title = "Display Limit", group=_displayStyleGrp, tooltip="")
// --
_styleGrp = "Line Styles and Colors"
_highLineStyle  = input.string("Solid", title = "High Line Style", options=["Solid", "Dashed", "Dotted"], group=_styleGrp)
highLineStyle = _highLineStyle=="Solid" ? line.style_solid : _highLineStyle=="Dashed" ? line.style_dashed : line.style_dotted
_lowLineStyle  = input.string("Solid", title = "Low Line Style", options=["Solid", "Dashed", "Dotted"], group=_styleGrp)
lowLineStyle = _lowLineStyle=="Solid" ? line.style_solid : _lowLineStyle=="Dashed" ? line.style_dashed : line.style_dotted
lineWidth  = input.int(1, title = "Line Width", group=_styleGrp, tooltip="")

// --
highLineColor = input.color(#1f4ef5, "High Line   ", group = _styleGrp, inline = "1")
lowLineColor = input.color(#fd441c, "Low Line", group = _styleGrp, inline = "1")
highBoxBgColor = input.color(color.new(#1f4ef5, 80), "High Box Bg ", group = _styleGrp, inline = "2")
highBoxBorderColor = input.color(color.new(#1f4ef5, 80), "Box Border", group = _styleGrp, inline = "2")
lowBoxBgColor = input.color(color.new(#fd441c, 80), "Low Box Bg  ", group = _styleGrp, inline = "3")
lowBoxBorderColor = input.color(color.new(#fd441c, 80), "Box Border", group = _styleGrp, inline = "3")
// --
// --
_styleGrpHTF = "Line Styles and Colors - Higher TimeFrame"
_highLineStyleHTF  = input.string("Solid", title = "High Line HTF", options=["Solid", "Dashed", "Dotted"], group=_styleGrpHTF)
highLineStyleHTF = _highLineStyleHTF=="Solid" ? line.style_solid : _highLineStyleHTF=="Dashed" ? line.style_dashed : line.style_dotted
_lowLineStyleHTF  = input.string("Solid", title = "Low Line HTF", options=["Solid", "Dashed", "Dotted"], group=_styleGrpHTF)
lowLineStyleHTF = _lowLineStyleHTF=="Solid" ? line.style_solid : _lowLineStyleHTF=="Dashed" ? line.style_dashed : line.style_dotted
lineWidthHTF  = input.int(1, title = "Line Width HTF", group=_styleGrpHTF, tooltip="")

// --
highLineColorHTF = input.color(#4c9650, "High Line   ", group = _styleGrpHTF, inline = "1")
lowLineColorHTF = input.color(#fd1c49, "Low Line", group = _styleGrpHTF, inline = "1")
highBoxBgColorHTF = input.color(color.new(#4c9650, 80), "High Box Bg ", group = _styleGrpHTF, inline = "2")
highBoxBorderColorHTF = input.color(color.new(#4c9650, 80), "Box Border", group = _styleGrpHTF, inline = "2")
lowBoxBgColorHTF = input.color(color.new(#fd1c49, 80), "Low Box Bg  ", group = _styleGrpHTF, inline = "3")
lowBoxBorderColorHTF = input.color(color.new(#fd1c49, 80), "Box Border", group = _styleGrpHTF, inline = "3")

// --
// --
// Alerts
alertNewHigh = input.bool(true, title = "New High", inline="1", group="Alerts")
alertNewHighTxt = input.string("Break out!", title="", inline="1", group="Alerts")

alertNewLow  = input.bool(true, title = "New Low", inline="2", group="Alerts")
alertNewLowTxt = input.string("Break down!", title="", inline="2", group="Alerts")

alertNewHighHTF  = input.bool(true, title = "New HTF High", inline="3", group="Alerts")
alertNewHighHTFTxt = input.string("Break out!", title="", inline="3", group="Alerts")

alertNewLowHTF  = input.bool(true, title = "New HTF Low", inline="4", group="Alerts")
alertNewLowHTFTxt = input.string("Break down!", title="", inline="4", group="Alerts")


// --
// ----------------------------------------------------
// Functions 
// ----------------------------------------------------
tf_multi(tf) =>
    ts = timeframe.in_seconds("")
    htfs = timeframe.in_seconds(tf)
    htfs/ts

display_limit_line(_array) =>
    if array.size(_array) > displayLimit/2
        a = array.shift(_array)
        line.delete(a)

display_limit_box(_array) =>
    if array.size(_array) > displayLimit/2
        a = array.shift(_array)
        box.delete(a)

remove_mitigated_lines(_array, _hl) =>
    m = false
    if array.size(_array) > 0 and removeMitigated        
        for i = array.size(_array) - 1 to 0 by 1
            l = array.get(_array, i)
            hh = _candleType == "Close" ? close[1] : high
            ll = _candleType == "Close" ? close[1] : low
            if _hl == "High" and hh > line.get_y1(l)
                array.remove(_array, i)
                if mitiOptions == "Show"
                    line.new(line.get_x1(l),line.get_y1(l),time,line.get_y1(l), xloc=xloc.bar_time, color = color.new(highLineColor, 70))
                line.delete(l)
                m := true
            if _hl == "Low" and ll < line.get_y1(l)
                array.remove(_array, i)
                if mitiOptions == "Show"
                    line.new(line.get_x1(l),line.get_y1(l),time,line.get_y1(l), xloc=xloc.bar_time, color = color.new(lowLineColor, 70))
                line.delete(l) 
                m := true  
    display_limit_line(_array) 
    m

remove_mitigated_boxes(_array, _hl) =>
    m = false
    if array.size(_array) > 0 and removeMitigated
        for i = array.size(_array) - 1 to 0 by 1
            l = array.get(_array, i)
            hh = _candleType == "Close" ? close[1] : high
            ll = _candleType == "Close" ? close[1] : low
            if _hl == "High" and hh > box.get_top(l)
                array.remove(_array, i)
                if mitiOptions == "Show"
                    box.new(box.get_left(l),box.get_top(l),time,box.get_bottom(l), xloc=xloc.bar_time, bgcolor = color.new(highBoxBgColor, 90), border_color = color.new(highBoxBorderColor, 90), border_style = highLineStyle)
                box.delete(l)
                m := true
            if _hl == "Low" and ll < box.get_top(l)
                array.remove(_array, i)
                if mitiOptions == "Show"
                    box.new(box.get_left(l),box.get_top(l),time,box.get_bottom(l), xloc=xloc.bar_time, bgcolor = color.new(lowBoxBgColor, 90), border_color = color.new(lowBoxBorderColor, 90), border_style = lowLineStyle)
                box.delete(l)
                m := true
    display_limit_box(_array) 
    m

extend_line_to_current(lineArray) =>
    if array.size(lineArray) > 0
        for i = array.size(lineArray) - 1 to 0 by 1
            l = array.get(lineArray, i)
            timeExt = time + ((time[1]-time[2])*20)
            line.set_x2(l, timeExt)

extend_box_to_current(boxArray) =>
    if array.size(boxArray) > 0
        for i = array.size(boxArray) - 1 to 0 by 1
            b = array.get(boxArray, i)
            timeExt = time + ((time[1]-time[2])*20)
            box.set_right(b, timeExt)

// ----------------------------------------------------
// Current TimeFrame
// ----------------------------------------------------
// Varibles 
// Lines
var highLineArray = array.new_line()
var lowLineArray = array.new_line()
// Boxes
var highBoxArray = array.new_box()
var lowBoxArray = array.new_box()

// Pivots
pivotHigh = ta.pivothigh(leftBars, rightBars)[1]
pivotLow = ta.pivotlow(leftBars, rightBars)[1]

// Run Calculations
if currentTF
    if pivotHigh
        if displayStyle == "Lines"
            array.push(highLineArray, line.new(time[rightBars+1],high[rightBars+1],time[+1],high[rightBars+1],color = highLineColor, style=highLineStyle, xloc=xloc.bar_time, extend=extentionMax?extend.right:extend.none, width = lineWidth))
        else
            y1 = math.max(open[rightBars+1], close[rightBars+1])
            array.push(highBoxArray, box.new(time[rightBars+1],high[rightBars+1],time[+1],y1,bgcolor = highBoxBgColor, border_color=highBoxBorderColor, xloc=xloc.bar_time, border_style = highLineStyle, extend=extentionMax?extend.right:extend.none, border_width = lineWidth))        
    if pivotLow
        if displayStyle == "Lines"
            array.push(lowLineArray, line.new(time[rightBars+1],low[rightBars+1],time[+1],low[rightBars+1],color = lowLineColor, style=lowLineStyle, xloc=xloc.bar_time, extend=extentionMax?extend.right:extend.none, width = lineWidth))
        else
            y1 = math.min(open[rightBars+1], close[rightBars+1])
            array.push(lowBoxArray, box.new(time[rightBars+1],low[rightBars+1],time[+1],y1,bgcolor = lowBoxBgColor, border_color=lowBoxBorderColor, xloc=xloc.bar_time, border_style = lowLineStyle, extend=extentionMax?extend.right:extend.none, border_width = lineWidth))

// ----------------------------------------------------
// Run Functions
// ----------------------------------------------------
highLineAlert = remove_mitigated_lines(highLineArray, "High")
lowLineAlert = remove_mitigated_lines(lowLineArray, "Low")
highBoxAlert = remove_mitigated_boxes(highBoxArray, "High")
lowBoxAlert = remove_mitigated_boxes(lowBoxArray, "Low")

if extentionCurrent
    extend_line_to_current(highLineArray)
    extend_line_to_current(lowLineArray)
    extend_box_to_current(highBoxArray)
    extend_box_to_current(lowBoxArray)

// Alerts
alertcondition(highLineAlert or highBoxAlert, "New High", "Price is breaking out!")
alertcondition(lowLineAlert or lowBoxAlert, "New Low", "Price is breaking down!")
//
if (highLineAlert or highBoxAlert) and alertNewHigh
    alert(alertNewHighTxt, alert.freq_once_per_bar)

if (lowLineAlert or lowBoxAlert) and alertNewLow
    alert(alertNewLowTxt, alert.freq_once_per_bar)


// ----------------------------------------------------
// Higher TimeFrame
// ----------------------------------------------------
// Varibles 
// Lines
var highLineArrayHTF = array.new_line()
var lowLineArrayHTF = array.new_line()
// Boxes
var highBoxArrayHTF = array.new_box()
var lowBoxArrayHTF = array.new_box()

// Get HTF
[_time, _open, _high, _low, _close] = request.security(syminfo.tickerid, htfTF, [time, open, high, low, close])

// Pivots
pivotHighHTF = ta.pivothigh(_high, leftBars*tf_multi(htfTF), rightBars+tf_multi(htfTF))
pivotLowHTF = ta.pivotlow(_low, leftBars*tf_multi(htfTF), rightBars+tf_multi(htfTF))

if htfBool
    timeExt = time+((time[1]-time[2])*10)
    dis = rightBars+tf_multi(htfTF)
    if pivotHighHTF
        if displayStyle == "Lines"
            array.push(highLineArrayHTF, line.new(_time[dis],_high[dis],_time[+1],_high[dis],color = highLineColorHTF, style=highLineStyleHTF, xloc=xloc.bar_time, extend=extentionMax?extend.right:extend.none, width = lineWidthHTF))
        else
            y1 = math.max(_open[dis], _close[dis])
            array.push(highBoxArrayHTF, box.new(_time[dis],_high[dis],_time[+1],y1,bgcolor = highBoxBgColorHTF, border_color=highBoxBorderColorHTF, xloc=xloc.bar_time, border_style = highLineStyleHTF, extend=extentionMax?extend.right:extend.none, border_width = lineWidthHTF))  
    if pivotLowHTF
        if displayStyle == "Lines"
            array.push(lowLineArrayHTF, line.new(_time[dis],_low[dis],_time[+1],_low[dis],color = lowLineColorHTF, style=lowLineStyleHTF, xloc=xloc.bar_time, extend=extentionMax?extend.right:extend.none, width = lineWidthHTF))
        else
            y1 = math.min(_open[dis], _close[dis])
            array.push(lowBoxArrayHTF, box.new(_time[dis],_low[dis],_time[+1],y1,bgcolor = lowBoxBgColorHTF, border_color=lowBoxBorderColorHTF, xloc=xloc.bar_time, border_style = lowLineStyleHTF, extend=extentionMax?extend.right:extend.none, border_width = lineWidthHTF))

// ----------------------------------------------------
// Run Functions
// ----------------------------------------------------
highLineAlertHTF = remove_mitigated_lines(highLineArrayHTF, "High")
lowLineAlertHTF = remove_mitigated_lines(lowLineArrayHTF, "Low")
highBoxAlertHTF = remove_mitigated_boxes(highBoxArrayHTF, "High")
lowBoxAlertHTF = remove_mitigated_boxes(lowBoxArrayHTF, "Low")

if extentionCurrent
    extend_line_to_current(highLineArrayHTF)
    extend_line_to_current(lowLineArrayHTF)
    extend_box_to_current(highBoxArrayHTF)
    extend_box_to_current(lowBoxArrayHTF)

// Alerts
alertcondition(highLineAlertHTF or highBoxAlertHTF, "New HTF High", "Price is breaking out!")
alertcondition(lowLineAlertHTF or lowBoxAlertHTF, "New HTF Low", "Price is breaking down!")
//
if (highLineAlertHTF or highBoxAlertHTF) and alertNewHighHTF
    alert(alertNewHighHTFTxt, alert.freq_once_per_bar)

if (lowLineAlertHTF or lowBoxAlertHTF) and alertNewLowHTF
    alert(alertNewLowHTFTxt, alert.freq_once_per_bar)
