// 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)



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