Higher order Orderblocks + Breakerblocks + Range + Alerts
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © pmk07
//
// Acknowledgements/Reference:
//
// @rumpypumpydumpy - Higher Order Pivots
// https://www.tradingview.com/v/x2LlRvBe/
//
// @MarkMiddleton2020 - Order Blocks
// https://www.tradingview.com/v/GecN34Qq/
//
//@version=5

indicator(title='Order Blocks', overlay=true)

bool        pv2_sv          = input.bool        (true,                          title='Plot 2nd order pivots')
bool        msb_sv          = input.bool        (true,                          title='Plot MSB lines')
bool        box_sv          = input.bool        (true,                          title='Plot Orderblocks')
bool        m_sv            = input.bool        (true,                          title='Plot Breakerblocks')
bool        range_sv        = input.bool        (true,                          title='Plot Range')
bool        range_eq_sv     = input.bool        (true,                          title='Plot Range 0.5 Line')
bool        range_q_sv      = input.bool        (true,                          title='Plot Range 0.25 and 0.75 Lines')
bool        log_sv          = input.bool        (true,                          title='Use Log Scale')
bool        msb_a_sv        = input.bool        (true,                          title='Alert MSB')
bool        ob_a_sv         = input.bool        (true,                          title='Alert Orderblock test')
bool        bb_a_sv         = input.bool        (true,                          title='Alert Breakerblock test')
bool        r_a_sv          = input.bool        (true,                          title='Alert New Range')
bool        rt_a_sv         = input.bool        (true,                          title='Alert Range test')
color       u_s             = input.color       (color.rgb(192, 192, 192, 80),  title='Untested Supply Color')
color       t_s             = input.color       (color.rgb(255, 0, 0, 80),      title='Tested Supply Color')
color       u_d             = input.color       (color.rgb(192, 192, 192, 80),  title='Untested Demand Color')
color       t_d             = input.color       (color.rgb(0, 255, 0, 80),      title='Tested Demand Color')
color       u_b             = input.color       (color.rgb(192, 192, 192, 80),  title='Untested Breaker Color')
color       t_b             = input.color       (color.new(color.blue, 80),     title='Tested Breaker Color')

var float[] pvh1_price      = array.new_float   (30, na)            // high
var int[]   pvh1_time       = array.new_int     (30, na)
var float[] pvl1_price      = array.new_float   (30, na)            // low
var int[]   pvl1_time       = array.new_int     (30, na)
var float[] pvh2_price      = array.new_float   (10, na)            // higher high
var int[]   pvh2_time       = array.new_int     (10, na)
var float[] pvl2_price      = array.new_float   (10, na)            // lower low
var int[]   pvl2_time       = array.new_int     (10, na)
var float   htcmrll_price   = na                                    // high that created most recent ll
var int     htcmrll_time    = na
var float   ltcmrhh_price   = na                                    // low that created most recent hh
var int     ltcmrhh_time    = na
var box[]   long_boxes      = array.new_box()                       // orderblocks
var box[]   short_boxes     = array.new_box()
var box[]   m_long_boxes    = array.new_box()                       // breakerblocks
var box[]   m_short_boxes   = array.new_box()
var line[]  bull_bos_lines  = array.new_line()                      // MSB lines
var line[]  bear_bos_lines  = array.new_line()
var line[]  range_h_lines   = array.new_line()                      // Range lines
var line[]  range_25_lines  = array.new_line()
var line[]  range_m_lines   = array.new_line()
var line[]  range_75_lines  = array.new_line()
var line[]  range_l_lines   = array.new_line()
var label[] la_ph2          = array.new_label()                     // 2nd order pivots
var label[] la_pl2          = array.new_label()
var float   temp_pv_0       = na
var float   temp_pv_1       = na
var float   temp_pv_2       = na
var int     temp_time       = na
var float   last_range_h    = na
var float   last_range_l    = na
var line    range_m         = na
var line    range_25        = na
var line    range_75        = na
var float   box_top         = na
var float   box_bottom      = na
var int     h_a_time        = 0
var int     l_a_time        = 0
var int     mh_a_time       = 0
var int     ml_a_time       = 0
var int     rh_a_time       = 0
var int     rl_a_time       = 0
bool        pvh             = high < high[1] and high[1] > high[2]
bool        pvl             = low > low[1] and low[1] < low[2]
int         pv1_time        = bar_index[1]
float       pv1_high        = high[1]
float       pv1_low         = low[1]
bool        new_ph_2nd      = false
bool        new_pl_2nd      = false
string      alert           = na

if barstate.isconfirmed
    if pvh
        array.pop(pvh1_price)
        array.pop(pvh1_time)
        array.unshift(pvh1_price, pv1_high)
        array.unshift(pvh1_time, pv1_time)
        if array.size(pvh1_price) > 2
            temp_pv_0 := array.get(pvh1_price, 0)
            temp_pv_1 := array.get(pvh1_price, 1)
            temp_pv_2 := array.get(pvh1_price, 2)
            if temp_pv_0 < temp_pv_1 and temp_pv_1 > temp_pv_2
                array.pop(pvh2_price)
                array.pop(pvh2_time)
                array.unshift(pvh2_price, temp_pv_1)
                array.unshift(pvh2_time, array.get(pvh1_time, 1))
                new_ph_2nd := true
                if temp_pv_1 > array.get(pvh2_price, 1)
                    for i = 0 to array.size(pvl2_time) - 1 by 1
                        temp_ltcmrhh_time = array.get(pvl2_time, i)
                        if temp_ltcmrhh_time < array.get(pvh2_time, 0)
                            ltcmrhh_price := array.get(pvl2_price, i)
                            ltcmrhh_time := temp_ltcmrhh_time
                            break
            if temp_pv_0 < ltcmrhh_price
                if msb_sv
                    array.push(bear_bos_lines, line.new(x1=ltcmrhh_time, y1=ltcmrhh_price, x2=bar_index, y2=ltcmrhh_price, color=color.green, width=2))
                box_top := array.get(pvh2_price, 0)
                box_bottom := math.max(low[bar_index - array.get(pvh2_time, 0)], low[bar_index - array.get(pvh2_time, 0) + 1])
                array.push(short_boxes, box.new(left=array.get(pvh2_time, 0), top=box_top, right=bar_index, bottom=box_bottom, bgcolor= box_sv ? u_s : na , border_color=na, extend=extend.right))
                if msb_a_sv
                    alert := alert + 'Bearish MSB @ ' + str.tostring(ltcmrhh_price) + '\n' + 'New Supply Zone : '+ str.tostring(box_top) + ' - ' + str.tostring(box_bottom) + '\n'
                ltcmrhh_price := na
    if pvl
        array.pop(pvl1_price)
        array.pop(pvl1_time)
        array.unshift(pvl1_price, pv1_low)
        array.unshift(pvl1_time, pv1_time)
        if array.size(pvl1_price) > 2
            temp_pv_0 := array.get(pvl1_price, 0)
            temp_pv_1 := array.get(pvl1_price, 1)
            temp_pv_2 := array.get(pvl1_price, 2)
            if temp_pv_0 > temp_pv_1 and temp_pv_1 < temp_pv_2
                array.pop(pvl2_price)
                array.pop(pvl2_time)
                array.unshift(pvl2_price, temp_pv_1)
                array.unshift(pvl2_time, array.get(pvl1_time, 1))
                new_pl_2nd := true
                if temp_pv_1 < array.get(pvl2_price, 1)
                    for i = 0 to array.size(pvh2_time) - 1 by 1
                        temp_htcmrll_time = array.get(pvh2_time, i)
                        if temp_htcmrll_time < array.get(pvl2_time, 0)
                            htcmrll_price := array.get(pvh2_price, i)
                            htcmrll_time := temp_htcmrll_time
                            break
            if temp_pv_0 > htcmrll_price
                if msb_sv
                    array.push(bull_bos_lines, line.new(x1=htcmrll_time, y1=htcmrll_price, x2=bar_index, y2=htcmrll_price, color=color.red, width=2))
                box_top := math.min(high[bar_index - array.get(pvl2_time, 0)], high[bar_index - array.get(pvl2_time, 0) + 1])
                box_bottom := array.get(pvl2_price, 0)
                array.push(long_boxes, box.new(left=array.get(pvl2_time, 0), top=box_top, right=bar_index, bottom=box_bottom, bgcolor= box_sv ? u_d : na, border_color=na, extend=extend.right))
                if msb_a_sv
                    alert := alert + 'Bullish MSB @ ' + str.tostring(htcmrll_price) + '\n' + 'New Demand Zone : '+ str.tostring(box_bottom) + ' - ' + str.tostring(box_top) + '\n'
                htcmrll_price := na
    if array.size(short_boxes) > 0
        for i = array.size(short_boxes) - 1 to 0 by 1
            tbox = array.get(short_boxes, i)
            top = box.get_top(tbox)
            bottom = box.get_bottom(tbox)
            ago = box.get_left(tbox)
            if array.get(pvh1_price, 0) > bottom 
                if box_sv
                    box.set_bgcolor(tbox, t_s)    
                if ob_a_sv and close < bottom
                    if array.get(pvh1_time, 0) != h_a_time
                        h_a_time := array.get(pvh1_time, 0)
                        alert := alert + 'Supply Zone Test @ ' + str.tostring(array.get(pvh1_price, 0)) + ' (age = ' + str.tostring(bar_index-ago) + ' bars) \n'
            if array.get(pvl1_price, 0) > top
                if m_sv
                    box.set_bgcolor(tbox, u_b)
                    array.push(m_long_boxes, tbox)
                else
                    box.delete(tbox)
                array.remove(short_boxes, i)
                if msb_sv    
                    line.delete(array.get(bear_bos_lines, i))
                    array.remove(bear_bos_lines, i)
    if array.size(long_boxes) > 0
        for i = array.size(long_boxes) - 1 to 0 by 1
            lbox = array.get(long_boxes, i)
            top = box.get_top(lbox)
            bottom = box.get_bottom(lbox)
            ago = box.get_left(lbox)
            if array.get(pvl1_price, 0) < top
                if box_sv
                    box.set_bgcolor(lbox, t_d)
                if ob_a_sv and close > top
                    if array.get(pvl1_time, 0) != l_a_time
                        l_a_time := array.get(pvl1_time, 0)
                        alert := alert + 'Demand Zone Test @ ' + str.tostring(array.get(pvl1_price, 0)) + ' (age = ' + str.tostring(bar_index-ago) + ' bars) \n'
            if array.get(pvh1_price, 0) < bottom
                if m_sv
                    box.set_bgcolor(lbox, u_b)
                    array.push(m_short_boxes, lbox)
                else
                    box.delete(lbox)
                array.remove(long_boxes, i)
                if msb_sv
                    line.delete(array.get(bull_bos_lines, i))
                    array.remove(bull_bos_lines, i)
    if array.size(m_short_boxes) > 0
        for i = array.size(m_short_boxes) - 1 to 0 by 1
            tbox = array.get(m_short_boxes, i)
            top = box.get_top(tbox)
            bottom = box.get_bottom(tbox)
            ago = box.get_left(tbox)
            if array.get(pvh1_price, 0) > bottom 
                box.set_bgcolor(tbox, t_b)
                if bb_a_sv and close < bottom
                    if array.get(pvh1_time, 0) != mh_a_time
                        mh_a_time := array.get(pvh1_time, 0)
                        alert := alert + 'Breakerblock Test Up @ ' + str.tostring(array.get(pvh1_price, 0)) + ' (age = ' + str.tostring(bar_index-ago) + ' bars) \n'
            if array.get(pvl1_price, 0) > top
                box.delete(tbox)
                array.remove(m_short_boxes, i)
    if array.size(m_long_boxes) > 0
        for i = array.size(m_long_boxes) - 1 to 0 by 1
            lbox = array.get(m_long_boxes, i)
            top = box.get_top(lbox)
            bottom = box.get_bottom(lbox)
            ago = box.get_left(lbox)
            if array.get(pvl1_price, 0) < top
                box.set_bgcolor(lbox, t_b)
                if bb_a_sv and close > top
                    if array.get(pvl1_time, 0) != ml_a_time
                        ml_a_time := array.get(pvl1_time, 0)
                        alert := alert + 'Breakerblock Test Down @ ' + str.tostring(array.get(pvl1_price, 0)) + ' (age = ' + str.tostring(bar_index-ago) + ' bars) \n'
            if array.get(pvh1_price, 0) < bottom
                box.delete(lbox)
                array.remove(m_long_boxes, i)
    if range_sv and (new_ph_2nd or new_pl_2nd) and (array.get(pvh2_price, 0) < array.get(pvh2_price, 1) and array.get(pvl2_price, 0) > array.get(pvl2_price, 1) and array.get(pvh2_price, 0) > array.get(pvl2_price, 1) and array.get(pvl2_price, 0) < array.get(pvh2_price, 1)) and (array.get(pvl2_price, 1) > nz(last_range_h) or na(last_range_l)? true : (array.get(pvh2_price, 1) < last_range_l))
        temp_time := math.min(array.get(pvh2_time, 1), array.get(pvl2_time, 1))
        last_range_h    := array.get(pvh2_price, 1)
        last_range_l    := array.get(pvl2_price, 1)
        temp_pv_0 := log_sv ? math.exp((math.log(last_range_h) + math.log(last_range_l))/2) : (last_range_h + last_range_l)/2
        temp_pv_1 := log_sv ? math.exp((math.log(last_range_h) + math.log(temp_pv_0))/2) : (last_range_h + temp_pv_0)/2
        temp_pv_2 := log_sv ? math.exp((math.log(last_range_l) + math.log(temp_pv_0))/2) : (last_range_l + temp_pv_0)/2
        array.push(range_h_lines, line.new(x1=temp_time, y1=last_range_h, x2=bar_index, y2=last_range_h, color=color.gray, width=1, extend=extend.right))
        array.push(range_l_lines, line.new(x1=temp_time, y1=last_range_l, x2=bar_index, y2=last_range_l, color=color.gray, width=1, extend=extend.right))
        if range_eq_sv
            array.push(range_m_lines, line.new(x1=temp_time, y1=temp_pv_0, x2=bar_index, y2=temp_pv_0, color=color.gray, width=1, extend=extend.right))
        if range_q_sv
            array.push(range_25_lines, line.new(x1=temp_time, y1=temp_pv_1, x2=bar_index, y2=temp_pv_1, style=line.style_dashed, color=color.gray, width=1, extend=extend.right))
            array.push(range_75_lines, line.new(x1=temp_time, y1=temp_pv_2, x2=bar_index, y2=temp_pv_2, style=line.style_dashed, color=color.gray, width=1, extend=extend.right))
        if r_a_sv
            alert := alert + 'New Range : ' + str.tostring(last_range_h) + ' - ' +  str.tostring(last_range_l) + '. Mean = ' +  str.tostring(temp_pv_0) + '\n'
    if array.size(range_h_lines) > 0
        for i = array.size(range_h_lines) - 1 to 0 by 1
            range_h = array.get(range_h_lines, i)
            top = line.get_y1(range_h)
            range_l = array.get(range_l_lines, i)
            bottom = line.get_y1(range_l)
            temp_time := line.get_x1(range_h)
            if array.get(pvh1_price, 0) > top
                if rt_a_sv and close < top
                    if array.get(pvh1_time, 0) != rh_a_time
                        rh_a_time := array.get(pvh1_time, 0)
                        alert := alert + 'Range High Test @ ' + str.tostring(array.get(pvh1_price, 0)) + ' \n'
            if array.get(pvl1_price, 0) < bottom
                if rt_a_sv and close > bottom
                    if array.get(pvl1_time, 0) != rl_a_time
                        rl_a_time := array.get(pvl1_time, 0)
                        alert := alert + 'Range Low Test @ ' + str.tostring(array.get(pvl1_price, 0)) + ' \n'
            if range_eq_sv
                range_m := array.get(range_m_lines, i)
            if range_q_sv
                range_25 := array.get(range_25_lines, i)
                range_75 := array.get(range_75_lines, i)
            if array.get(pvh1_price, 0) < bottom or array.get(pvl1_price, 0) > top
                line.delete(range_h)
                array.remove(range_h_lines, i)
                line.delete(range_l)
                array.remove(range_l_lines, i)
                if range_eq_sv
                    line.delete(range_m)                
                    array.remove(range_m_lines, i)
                if range_q_sv
                    line.delete(range_25)                
                    array.remove(range_25_lines, i)
                    line.delete(range_75)                
                    array.remove(range_75_lines, i)
                last_range_h    := na
                last_range_l    := na
    if pv2_sv
        if new_ph_2nd
            array.push(la_ph2, label.new(x = array.get(pvh2_time, 0), y = array.get(pvh2_price, 0), xloc = xloc.bar_index, style = label.style_label_down,    color = #770000FF, size = size.tiny))
        if new_pl_2nd
            array.push(la_pl2, label.new(x = array.get(pvl2_time, 0), y = array.get(pvl2_price, 0), xloc = xloc.bar_index, style = label.style_label_up,      color = #007700FF, size = size.tiny))

alert := not na(alert) ? (alert + 'Current price = ' + str.tostring(close) + '\n') : na
exec = not na(alert) ? true : false
if exec==true 
    alert(alert, alert.freq_once_per_bar_close)