Market Structure MA Based BOS [liwei666]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © liwei666
//@version=5
// # ========================================================================= #
// #                   | ma_based_bos  Indicator  |
// # ========================================================================= #

indicator(
 title                = "Market Structure MA Based BOS",
 shorttitle           = "ma_based_bos",
 overlay              =  true,
 max_lines_count                 =  500, 
 max_labels_count                =  500, 
 max_boxes_count                 =  500,
 max_bars_back = 5000
 )
// # ========================================================================= #
// #                   |  ma_based_bos Indicator  |
// # ========================================================================= #

// --------------- Inputs ---------------

smoothing = input.string(title="MA_Type", defval="HMA", options=["RMA", "SMA", "EMA", "WMA", "HMA"], group = 'GRP1')
short_ma_len = input.int(defval = 30, title = "short_ma_len", minval = 5 , maxval = 240 , step = 1 , tooltip = "短MA长度", inline = "11", group = 'GRP1')

show_short_zz = input(false, title='Show short-len Zigzag', inline = "22", group='GRP2')
show_ma_cross_signal = input(false, title='Show ma cross signal', inline = "22", group='GRP2')
zzstyle = input.string(defval='Dashed', title='Zig Zag Line Styles', options=['Dashed', 'Dotted', 'Solid'], group='GRP2')
// --------------- Inputs ---------------

// --------------- vars ---------------

var max_arr_size = 300
var short_ma_color = color.red
short_ma_color := nz(short_ma_color[1], color.red)
var line short_zzline = na

var line short_bosline = na
var label short_bos_lb = na
color label_transp = #ffffff00

var short_pivot_l_arr = array.new_float(size = max_arr_size, initial_value = 0.0) 
var short_pivot_h_arr = array.new_float(size = max_arr_size, initial_value = 0.0)
var short_lowestestbar_offset_arr = array.new_int(size = max_arr_size, initial_value = 0) 
var short_highestbar_offset_arr   = array.new_int(size = max_arr_size, initial_value = 0)

var s_bos_high_arr = array.new_bool(size = 1, initial_value = false)
var s_bos_low_arr = array.new_bool(size = 1, initial_value = false)

// --------------- vars ---------------

// --------------- Functions ---------------
add_to_zigzag(arr, value) =>
    array.unshift(arr, value)
    if array.size(arr) > max_arr_size
        array.pop(arr)


ma_function(source, length) =>
    float ma = switch smoothing
        "RMA" => nz(ta.rma(source, length), source)
        "SMA" => nz(ta.sma(source, length), source)
        "EMA" => nz(ta.ema(source, length), source)
        "HMA" => nz(ta.hma(source, length), source)
        "WMA" => nz(ta.wma(source, length), source)
        =>  nz(ta.wma(source, length), source)
    ma

is_first_break(bos_arr, i) =>
    val = array.get(id = bos_arr, index = i)
    if val == true
        false
    else
        true


// --------------- Functions ---------------

// --------------- Calculate Pivots ---------------
short_ma = ma_function(close, short_ma_len)

// -- short ma calulate
var s_ma_continue_len = 2
s_ma_continue_len := nz(s_ma_continue_len[1], 2)
int short_highestbar_offset = - ta.highestbars(high, s_ma_continue_len)
int short_lowestbar_offset = - ta.lowestbars(low, s_ma_continue_len)
float short_pivot_h = ta.highest(high, s_ma_continue_len)
float short_pivot_l = ta.lowest(low, s_ma_continue_len)

short_ma_cu = ta.crossunder(source1 = close, source2 = short_ma) 
short_ma_co = ta.crossover(source1 = close, source2 = short_ma)

bool alert_bos_high = false
bool alert_bos_low = false

if short_ma_co
    // 上穿找 lowest
    add_to_zigzag(short_pivot_l_arr, short_pivot_l)
    add_to_zigzag(short_lowestestbar_offset_arr, short_lowestbar_offset)

    short_ma_color := color.new(#0cdb6d, 10)


    barindex0 = math.round(array.get(short_lowestestbar_offset_arr, 0))
    price0 = array.get(short_pivot_l_arr, 0)
    barindex1 = math.round(array.get(short_highestbar_offset_arr, 0)) + s_ma_continue_len
    price1 = array.get(short_pivot_h_arr, 0)
    
    if show_short_zz
        short_zzline := line.new(x1 = bar_index[barindex0], y1 = price0, x2 = bar_index[barindex1], y2 = price1, color = color.red, style = line.style_solid ) 

        // labelText = 'H,' + str.tostring(price0) + ',' + str.tostring(barindex0) + ',' + str.tostring(price1) + ',' +  str.tostring(barindex1)
        // label.new(x = bar_index[short_lowestbar_offset], y = short_pivot_l , text = labelText,  yloc = yloc.belowbar , style=label.style_label_up, textcolor = color.white)
    
    // 将barindex更新，下面的判断BOS需要用
    array.set(short_highestbar_offset_arr, 0, barindex1)
    s_ma_continue_len := 1

    // 重置突破前低为初始值
    array.fill(s_bos_low_arr, false)
else if short_ma_cu
    // 下穿找 highest
    add_to_zigzag(short_pivot_h_arr, short_pivot_h)
    add_to_zigzag(short_highestbar_offset_arr, short_highestbar_offset)

    // 改颜色
    short_ma_color := color.red
    
    price0 = array.get(short_pivot_h_arr, 0)
    barindex0 = math.round(array.get(short_highestbar_offset_arr, 0))
    price1 = array.get(short_pivot_l_arr, 0)
    barindex1 = math.round(array.get(short_lowestestbar_offset_arr, 0)) + s_ma_continue_len
    
    
    if show_short_zz
        short_zzline := line.new(x1 = bar_index[barindex0], y1 = price0, x2 = bar_index[barindex1], y2 = price1, color = color.red, style = line.style_solid ) 

    // 将barindex更新，下面的判断BOS需要用    
    array.set(short_lowestestbar_offset_arr, 0, barindex1)
    s_ma_continue_len := 1
    // 重置突破前高为初始值
    array.fill(s_bos_high_arr, false)
else
    s_ma_continue_len := s_ma_continue_len + 1

    // 判断是否BOS
    // break ligh
    for i = 0 to array.size(s_bos_high_arr) - 1
        if (close[2] < array.get(short_pivot_h_arr, i) or close[1] < array.get(short_pivot_h_arr, i))  and  close > array.get(short_pivot_h_arr, i)
            // 判断是否首次突破
            if is_first_break(s_bos_high_arr, i)
                // 首次突破，赋值
                array.set(s_bos_high_arr, i , value = true) 

                price0 = array.get(short_pivot_h_arr, i)
                barindex0 = 0
                for j = 0 to i
                    barindex0 += array.get(short_highestbar_offset_arr, j)
                    if j == 0
                        barindex0 += s_ma_continue_len
                    
                price1 = array.get(short_pivot_h_arr, i)
                barindex1 = 0
                
                labelText = 'L,' + str.tostring(price0) + ',' + str.tostring(barindex0) + ','  +  str.tostring(barindex1)
                //label.new(x = bar_index[barindex0], y = price0 , text = labelText,  yloc = yloc.belowbar , style=label.style_label_up, textcolor = color.white)
                
                short_bosline := line.new(x1 = bar_index[barindex0], y1 = price0, x2 = bar_index[barindex1], y2 = price1,
                 color = color.new(#0cdb6d, 10), style = line.style_solid, width=1) 
                short_bos_lb  := label.new(int(math.avg(bar_index[barindex0],bar_index[barindex1] )), price1, color = label_transp, text="BOS",
                 textcolor = color.new(#0cdb6d, 10), size=size.small, style = label.style_label_down)
                alert_bos_high := true

    // 判断break low
    for i = 0 to array.size(s_bos_low_arr) - 1
        if (close[2] > array.get(short_pivot_l_arr, i) or close[1] > array.get(short_pivot_l_arr, i))  and  close < array.get(short_pivot_l_arr, i)
            // 判断是否首次突破
            if is_first_break(s_bos_low_arr, i)
                // 首次突破，赋值
                array.set(s_bos_low_arr, i , value = true) 

                price0 = array.get(short_pivot_l_arr, i)
                barindex0 = 0
                for j = 0 to i
                    barindex0 += array.get(short_lowestestbar_offset_arr, j)
                    if j == 0
                        barindex0 += s_ma_continue_len
                    
                price1 = array.get(short_pivot_l_arr, i)
                barindex1 = 0
                
                labelText = 'H,' + str.tostring(price0) + ',' + str.tostring(barindex0) + ','  +  str.tostring(barindex1)
                //label.new(x = bar_index[barindex0], y = price0 , text = labelText,  yloc = yloc.abovebar , style=label.style_label_up, textcolor = color.white)
                
                short_bosline := line.new(x1 = bar_index[barindex0], y1 = price0, x2 = bar_index[barindex1], y2 = price1, color = color.red, style = line.style_solid ) 
                short_bos_lb  := label.new(int(math.avg(bar_index[barindex0],bar_index[barindex1] )), price1, color = label_transp, text="BOS",
                 textcolor = color.red, size=size.small, style = label.style_label_up)
                alert_bos_low := true 

plot(short_ma,color = short_ma_color)
// 画出短ma上下穿标识
short_ma_long_signal = short_ma_co and show_ma_cross_signal
plotchar(short_ma_long_signal, "s_co", "▲", location.belowbar, color = color.blue, size = size.tiny)
short_ma_short_signal = short_ma_cu and show_ma_cross_signal
plotchar(short_ma_short_signal, "s_cu", "▼", location.abovebar, color = color.red, size = size.tiny)

// add alert when bos-high or bos-low
alertcondition(alert_bos_high, title='bos_high', message='BOS High, {{ticker}}, {{close}}, {{timenow}}')
alertcondition(alert_bos_low, title='bos_low', message='BOS Low, {{ticker}}, {{close}}, {{timenow}}')