Market Structure CHoCH/BOS (Fractal) [LuxAlgo]
// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © LuxAlgo

//@version=5
indicator("Market Structure CHoCH/BOS (Fractal) [LuxAlgo]", "LuxAlgo - Market Structure (Fractal)", overlay = true, max_lines_count = 500, max_labels_count = 500)
//------------------------------------------------------------------------------
// Settings
//-----------------------------------------------------------------------------{
length = input.int(5, minval = 3)

//Colors
showBull = input(true, 'Bullish Structures', inline = 'bull', group = 'Style')
bullCss = input.color(#089981, '', inline = 'bull', group = 'Style')

showBear = input(true, 'Bearish Structures', inline = 'bear', group = 'Style')
bearCss = input.color(#f23645, '', inline = 'bear', group = 'Style')

showSupport = input(false, 'Support', inline = 's', group = 'Style')
supCss = input.color(#089981, '', inline = 's', group = 'Style')

showResistance = input(false, 'Resistance', inline = 'r', group = 'Style')
resCss = input.color(#f23645, '', inline = 'r', group = 'Style')

//Dashboard
showDash  = input(false, 'Show Dashboard', group = 'Dashboard')
dashLoc  = input.string('Top Right', 'Location', options = ['Top Right', 'Bottom Right', 'Bottom Left'], group = 'Dashboard')
textSize = input.string('Small', 'Size'        , options = ['Tiny', 'Small', 'Normal']                 , group = 'Dashboard')

//-----------------------------------------------------------------------------}
//Types
//-----------------------------------------------------------------------------{
type fractal
    float value
    int loc
    bool iscrossed

//-----------------------------------------------------------------------------}
//Fractal Detection
//-----------------------------------------------------------------------------{
var p = int(length/2)
n = bar_index

dh = math.sum(math.sign(high - high[1]), p)
dl = math.sum(math.sign(low - low[1]), p)

bullf = dh == -p and dh[p] == p and high[p] == ta.highest(length)
bearf = dl == p and dl[p] == -p and low[p] == ta.lowest(length)

bullf_count = ta.cum(bullf ? 1 : 0)
bearf_count = ta.cum(bearf ? 1 : 0)

//-----------------------------------------------------------------------------}
//Bullish market structure
//-----------------------------------------------------------------------------{
var upper = fractal.new()
var line lower_lvl = na
var label ms_lbl = na
var bull_ms_count = 0
var broken_sup = false
var os = 0

if bullf
    upper.value := high[p]
    upper.loc := n-p
    upper.iscrossed := false

if ta.crossover(close, upper.value) and not upper.iscrossed
    line.new(upper.loc, upper.value, n, upper.value, color = showBull ? bullCss : na)

    ms_lbl := label.new(int(math.avg(n, upper.loc)), upper.value, os == -1 ? 'ChoCH' : 'BOS'
      , color = color(na)
      , textcolor = showBull ? bullCss : na
      , style = label.style_label_down
      , size = size.tiny)

    //Set support
    k = 2
    min = low[1]
    for i = 2 to (n - upper.loc)-1
        min := math.min(low[i], min)
        k := low[i] == min ? i : k

    if showSupport
        lower_lvl := line.new(n-k, min, n, min, color = bullCss, style = line.style_dashed)
        broken_sup := false

    upper.iscrossed := true
    bull_ms_count += 1
    os := 1

else if showSupport and not broken_sup
    lower_lvl.set_x2(n)

    if close < lower_lvl.get_y2()
        broken_sup := true

//-----------------------------------------------------------------------------}
//Bearish market structure
//-----------------------------------------------------------------------------{
var lower = fractal.new()
var line upper_lvl = na
var broken_res = false
var bear_ms_count = 0

if bearf
    lower.value := low[p]
    lower.loc := n-p
    lower.iscrossed := false

if ta.crossunder(close, lower.value) and not lower.iscrossed
    line.new(lower.loc, lower.value, n, lower.value, color = showBear ? bearCss : na)
    
    label.new(int(math.avg(n, lower.loc)), lower.value, os == 1 ? 'ChoCH' : 'BOS'
      , color = color(na)
      , textcolor = showBear ? bearCss : na
      , style = label.style_label_up
      , size = size.tiny)

    //Set resistance
    k = 2
    max = high[1]
    for i = 2 to (n - lower.loc)-1
        max := math.max(high[i], max)
        k := high[i] == max ? i : k

    if showResistance
        upper_lvl := line.new(n-k, max, n, max, color = bearCss, style = line.style_dashed)
        broken_res := false
    
    lower.iscrossed := true
    bear_ms_count += 1
    os := -1

else if showResistance and not broken_res
    upper_lvl.set_x2(n)

    if close > upper_lvl.get_y2()
        broken_res := true

//-----------------------------------------------------------------------------}
//Dashboard
//-----------------------------------------------------------------------------{
var table_position = dashLoc == 'Bottom Left' ? position.bottom_left 
  : dashLoc == 'Top Right' ? position.top_right 
  : position.bottom_right

var table_size = textSize == 'Tiny' ? size.tiny 
  : textSize == 'Small' ? size.small 
  : size.normal

var tb = table.new(table_position, 2, 3
  , bgcolor = #1e222d
  , border_color = #373a46
  , border_width = 1
  , frame_color = #373a46
  , frame_width = 1)

if showDash
    if barstate.isfirst
        tb.cell(0, 0, 'Structure To Fractal %', text_color = color.white, text_size = table_size)
        tb.merge_cells(0,0,1,0)

        tb.cell(0, 1, 'Bullish', text_color = #089981, text_size = table_size)
        tb.cell(1, 1, 'Bearish', text_color = #f23645, text_size = table_size)
    
    if barstate.islast
        tb.cell(0, 2, str.tostring(bull_ms_count / bullf_count * 100, format.percent), text_color = #089981, text_size = table_size)
        tb.cell(1, 2, str.tostring(bear_ms_count / bearf_count * 100, format.percent), text_color = #f23645, text_size = table_size)

//-----------------------------------------------------------------------------}
//Plots
//-----------------------------------------------------------------------------{
plot(broken_res and not broken_res[1] ? low : na, 'Resistance Breakout', #089981, 2, plot.style_circles)
plot(broken_sup and not broken_sup[1] ? high : na, 'Support Breakout', #f23645, 2, plot.style_circles)

//-----------------------------------------------------------------------------}