Market Structure Patterns (Nephew_Sam_)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Nephew_Sam_

//@version=5
indicator('Market Structure (Nephew_Sam_)', overlay=true, max_bars_back=500, max_labels_count=500)


// --------------- INPUTS ---------------
var GRP1 = "Settings"
length = input.int(defval=5, title='Pivot strength', minval=2, maxval=20, group=GRP1)
showzz = input(false, title='Show Zigzag', group=GRP1)
showbosline = input(false, title='Show BOS Lines', group=GRP1)
showhhll = input(true, title='Show HH/LL', group=GRP1)
showPatternMatch = input(true, title='Show Pattern Matches', group=GRP1)

var GRP2 = "Bull Patterns"
bullCond1 = input.string(defval='LL,LH,LL,HH,HL', title='Bull 1', inline="11", group=GRP2)
bullTxt1 = input.string(defval='BOS HL 1', title='', inline="11", group=GRP2)
bullBool1 = input.bool(true, "", inline="11", group = GRP2)
bullCond2 = input.string(defval='LL,LH,HL,LH,LL,HH,HL', title='Bull 2', inline="22", group=GRP2)
bullTxt2 = input.string(defval='BOS HL 2', title='', inline="22", group=GRP2)
bullBool2 = input.bool(true, "", inline="22", group = GRP2)
bullCond3 = input.string(defval='LL,LH,LL,LH,HL,HH,HL', title='Bull 3', inline="33", group=GRP2)
bullTxt3 = input.string(defval='BOS HL 3', title='', inline="33", group=GRP2)
bullBool3 = input.bool(true, "", inline="33", group = GRP2)
bullCond4 = input.string(defval='', title='Bull 4', inline="44", group=GRP2)
bullTxt4 = input.string(defval='', title='', inline="44", group=GRP2)
bullBool4 = input.bool(true, "", inline="44", group = GRP2)
bullCond5 = input.string(defval='', title='Bull 5', inline="55", group=GRP2)
bullTxt5 = input.string(defval='', title='', inline="55", group=GRP2)
bullBool5 = input.bool(true, "", inline="55", group = GRP2)
bullCond6 = input.string(defval='', title='Bull 6', inline="66", group=GRP2)
bullTxt6 = input.string(defval='', title='', inline="66", group=GRP2)
bullBool6 = input.bool(true, "", inline="66", group = GRP2)
bullCond7 = input.string(defval='', title='Bull 7', inline="77", group=GRP2)
bullTxt7 = input.string(defval='', title='', inline="77", group=GRP2)
bullBool7 = input.bool(true, "", inline="77", group = GRP2)

var GRP3 = "Bear Patterns"
bearCond1 = input.string(defval='HH,HL,HH,LL,LH', title='Bear 1', inline="11", group=GRP3)
bearTxt1 = input.string(defval='BOS LH 1', title='', inline="11", group=GRP3)
bearBool1 = input.bool(true, "", inline="11", group = GRP3)
bearCond2 = input.string(defval='HH,HL,LH,HL,HH,LL,LH', title='Bear 2', inline="22", group=GRP3)
bearTxt2 = input.string(defval='BOS LH 2', title='', inline="22", group=GRP3)
bearBool2 = input.bool(true, "", inline="22", group = GRP3)
bearCond3 = input.string(defval='HH,HL,HH,HL,LH,LL,LH', title='Bear 3', inline="33", group=GRP3)
bearTxt3 = input.string(defval='BOS LH 3', title='', inline="33", group=GRP3)
bearBool3 = input.bool(true, "", inline="33", group = GRP3)
bearCond4 = input.string(defval='', title='Bear 4', inline="44", group=GRP3)
bearTxt4 = input.string(defval='', title='', inline="44", group=GRP3)
bearBool4 = input.bool(true, "", inline="44", group = GRP3)
bearCond5 = input.string(defval='', title='Bear 5', inline="55", group=GRP3)
bearTxt5 = input.string(defval='', title='', inline="55", group=GRP3)
bearBool5 = input.bool(true, "", inline="55", group = GRP3)
bearCond6 = input.string(defval='', title='Bear 6', inline="66", group=GRP3)
bearTxt6 = input.string(defval='', title='', inline="66", group=GRP3)
bearBool6 = input.bool(true, "", inline="66", group = GRP3)
bearCond7 = input.string(defval='', title='Bear 7', inline="77", group=GRP3)
bearTxt7 = input.string(defval='', title='', inline="77", group=GRP3)
bearBool7 = input.bool(true, "", inline="77", group = GRP3)

var GRP4 = "Styles"
upcol = input(defval=color.green, title='HHLL Up Color', group=GRP4)
dncol = input(defval=color.red, title='HHLL Down Color', group=GRP4)
uplabeltextcol = input(defval=color.white, title='Label Text Up Color', group=GRP4)
dnlabeltextcol = input(defval=color.white, title='Label Text Down Color', group=GRP4)
uplabelcol = input(defval=color.green, title='Label BG Up Color', group=GRP4)
dnlabelcol = input(defval=color.red, title='Label BG Down Color', group=GRP4)
zzstyle = input.string(defval='Dashed', title='Zig Zag Line Style', options=['Dashed', 'Dotted'], group=GRP4)
zzwidth = input.int(defval=1, title='Zig zag Line Width', minval=1, maxval=4, group=GRP4)

var max_array_size = 10
// --------------- INPUTS ---------------


// --------------- Functions ---------------
isMatching(_str, _arr) =>
    if _str == ""
        false
    else
        hhll_join = array.join(_arr, ",")
        _temp0 = str.replace_all(_str, " ", "")
        _temp1 = str.upper(_str)
        _temp2 = str.split(_str, ",")
        array.reverse(_temp2)
        _temp3 = array.join(_temp2, ",")
        str.startswith(hhll_join, _temp3)

addToString(_str, _val) =>
    _temp0 = _str == "" ? _str : _str + "\n"
    _temp0 + _val

add_to_zigzag(arr, value) =>
    array.unshift(arr, value)
    if array.size(arr) > max_array_size
        array.pop(arr)

update_zigzag(arr, value, direction, _skip=false) =>
    if array.size(arr) == 0
        add_to_zigzag(arr, value)
    else
        if _skip
            array.set(arr, 0, value)
        else if direction == "ph" and value > array.get(arr, 0) or direction == "pl" and value < array.get(arr, 0)
            array.set(arr, 0, value)
        0.
// --------------- Functions ---------------


// --------------- Calculate Pivots ---------------
float pivoth = ta.highestbars(high, length) == 0 ? high : na
float pivotl = ta.lowestbars(low, length) == 0 ? low : na

var direction = "na"
iff_ = pivotl and na(pivoth) ? "pl" : direction
direction := pivoth and na(pivotl) ? "ph" : iff_
// --------------- Calculate Pivots ---------------


// --------------- Zigzags vars ---------------
var zigzag_price = array.new_float(0)
oldzigzag_price = array.copy(zigzag_price)

var zigzag_barindex = array.new_int(0)
oldzigzag_barindex = array.copy(zigzag_barindex)

var zigzag_hhll = array.new_string(0)
oldzigzag_hhll = array.copy(zigzag_hhll)

direction_changed = direction != direction[1] //ta.change(direction)
if pivoth or pivotl
    if direction_changed
        add_to_zigzag(zigzag_price, direction == "ph" ? pivoth : pivotl)
        add_to_zigzag(zigzag_barindex, bar_index)

        if array.size(zigzag_price) >= 3
            _text = direction == "ph" ? array.get(zigzag_price, 0) > array.get(zigzag_price, 2) ? 'HH' : 'LH' : array.get(zigzag_price, 0) < array.get(zigzag_price, 2) ? 'LL' : 'HL'
            add_to_zigzag(zigzag_hhll, _text)
    else
        update_zigzag(zigzag_price, direction == "ph" ? pivoth : pivotl, direction)
        update_zigzag(zigzag_barindex, bar_index, direction, true)

        if array.size(zigzag_price) >= 3
            _text = direction == "ph" ? array.get(zigzag_price, 0) > array.get(zigzag_price, 2) ? 'HH' : 'LH' : array.get(zigzag_price, 0) < array.get(zigzag_price, 2) ? 'LL' : 'HL'
            array.set(zigzag_hhll, 0, _text)
// --------------- Zigzags vars ---------------


// --------------- Plot ---------------
if array.size(zigzag_price) >= 3 and array.size(zigzag_hhll) >= 3
    var line zzline = na
    var label zzlabel = na
    var label pattern_label_bull = na
    var label pattern_label_bear = na
    
    price0 = array.get(zigzag_price, 0)
    price1 = array.get(zigzag_price, 1)
    price2 = array.get(zigzag_price, 2)
    old_price0 = array.get(oldzigzag_price, 0)
    old_price1 = array.get(oldzigzag_price, 1)

    barindex0 = math.round(array.get(zigzag_barindex, 0))
    barindex1 = math.round(array.get(zigzag_barindex, 1))
    old_barindex0 = math.round(array.get(oldzigzag_barindex, 0))
    old_barindex1 = math.round(array.get(oldzigzag_barindex, 1))

    zigzag0 = array.get(zigzag_hhll, 0)
    zigzag1 = array.get(zigzag_hhll, 1)
    old_zigzag0 = array.get(oldzigzag_hhll, 0)
    old_zigzag1 = array.get(oldzigzag_hhll, 1)
    
    hhlltxt = direction == "ph" ? price0 > price2 ? 'HH' : 'LH' : price0 < price2 ? 'LL' : 'HL'
    txtcol = direction == "ph" ? price0 > price2 ? upcol : dncol : price0 < price2 ? dncol : upcol


    if price0 != old_price0 and barindex0 != old_barindex0
        // Update line and label
        if price1 == old_price1 and barindex1 == old_barindex1
            line.delete(zzline)
            label.delete(zzlabel)
            label.delete(pattern_label_bull)
            label.delete(pattern_label_bear)

        // plot lines
        if showzz
            zzline := line.new(
                 x1=barindex0, y1=price0,
                 x2=barindex1, y2=price1,
                 color=direction == "ph" ? upcol : dncol,
                 width=zzwidth,
                 style=zzstyle == 'Dashed' ? line.style_dashed : line.style_dotted)
        
        // plot labels
        if showhhll
            zzlabel := label.new(
                 x=barindex0,
                 y=price0,
                 text=hhlltxt,
                //  color=labelcol,
                 textcolor=txtcol,
                 yloc=direction == "ph" ? yloc.abovebar : yloc.belowbar,
                 style=label.style_none)
        
        // Plot pattern matches
        if showPatternMatch
            bull1Met = bullBool1 ? isMatching(bullCond1, zigzag_hhll) : false
            bull2Met = bullBool2 ? isMatching(bullCond2, zigzag_hhll) : false
            bull3Met = bullBool3 ? isMatching(bullCond3, zigzag_hhll) : false
            bull4Met = bullBool4 ? isMatching(bullCond4, zigzag_hhll) : false
            bull5Met = bullBool5 ? isMatching(bullCond5, zigzag_hhll) : false
            bull6Met = bullBool6 ? isMatching(bullCond6, zigzag_hhll) : false
            bull7Met = bullBool7 ? isMatching(bullCond7, zigzag_hhll) : false

            bear1Met = bearBool1 ? isMatching(bearCond1, zigzag_hhll) : false
            bear2Met = bearBool2 ? isMatching(bearCond2, zigzag_hhll) : false
            bear3Met = bearBool3 ? isMatching(bearCond3, zigzag_hhll) : false
            bear4Met = bearBool4 ? isMatching(bearCond4, zigzag_hhll) : false
            bear5Met = bearBool5 ? isMatching(bearCond5, zigzag_hhll) : false
            bear6Met = bearBool6 ? isMatching(bearCond6, zigzag_hhll) : false
            bear7Met = bearBool7 ? isMatching(bearCond7, zigzag_hhll) : false

            bullPatternText = ""
            bullPatternText := bull1Met ? addToString(bullPatternText, bullTxt1) : bullPatternText
            bullPatternText := bull2Met ? addToString(bullPatternText, bullTxt2) : bullPatternText
            bullPatternText := bull3Met ? addToString(bullPatternText, bullTxt3) : bullPatternText
            bullPatternText := bull4Met ? addToString(bullPatternText, bullTxt4) : bullPatternText
            bullPatternText := bull5Met ? addToString(bullPatternText, bullTxt5) : bullPatternText
            bullPatternText := bull6Met ? addToString(bullPatternText, bullTxt6) : bullPatternText
            bullPatternText := bull7Met ? addToString(bullPatternText, bullTxt7) : bullPatternText

            bearPatternText = ""
            bearPatternText := bear1Met ? addToString(bearPatternText, bearTxt1) : bearPatternText
            bearPatternText := bear2Met ? addToString(bearPatternText, bearTxt2) : bearPatternText
            bearPatternText := bear3Met ? addToString(bearPatternText, bearTxt3) : bearPatternText
            bearPatternText := bear4Met ? addToString(bearPatternText, bearTxt4) : bearPatternText
            bearPatternText := bear5Met ? addToString(bearPatternText, bearTxt5) : bearPatternText
            bearPatternText := bear6Met ? addToString(bearPatternText, bearTxt6) : bearPatternText
            bearPatternText := bear7Met ? addToString(bearPatternText, bearTxt7) : bearPatternText

        
            pattern_label_bull := label.new(
                 barindex0,
                 y=price0,
                 yloc=yloc.belowbar,
                 text=bullPatternText != "" ? bullPatternText : "",
                 color=uplabelcol,
                 textcolor=uplabeltextcol,
                 style=bullPatternText != "" ? label.style_label_up : label.style_none)
        
            pattern_label_bear := label.new(
                 barindex0,
                 y=price0,
                 yloc=yloc.abovebar,
                 text=bearPatternText != "" ? bearPatternText : "",
                 color=dnlabelcol,
                 textcolor=dnlabeltextcol,
                 style=bearPatternText != "" ? label.style_label_down : label.style_none)            
// --------------- Plot ---------------



// --------------- BOS ---------------
bosBearMatchPattern = "HL,HH,LL"
bosBullMatchPattern = "LH,LL,HH"

if array.size(zigzag_price) >= 3 and array.size(zigzag_hhll) >= 3 and showbosline
    isBullMatching = isMatching(bosBullMatchPattern, zigzag_hhll)
    isBearMatching = isMatching(bosBearMatchPattern, zigzag_hhll)

    if isBearMatching and not isBearMatching[1]
        _bi = array.get(zigzag_barindex, 2)
        line.new(bar_index[bar_index - _bi], low[bar_index - _bi], bar_index, low[bar_index - _bi], color=dncol)
        label.new(bar_index, low[bar_index - _bi], "bos         ", style=label.style_none, size=size.small, textcolor=dncol)
        

    if isBullMatching and not isBullMatching[1]
        _bi = array.get(zigzag_barindex, 2)
        line.new(bar_index[bar_index - _bi], high[bar_index - _bi], bar_index, high[bar_index - _bi], color=upcol)
        label.new(bar_index, high[bar_index - _bi], "bos         ", style=label.style_none, size=size.small, textcolor=upcol)
// --------------- BOS ---------------

// if barstate.islast
    // label.new(bar_index, low, str.tostring(zigzag_hhll), yloc=yloc.belowbar)
    
// WATERMARK
if barstate.islast    
    _table = table.new("bottom_left", 1, 2)
    table.cell(_table, 0, 0, text="@Nephew_Sam_", text_size=size.small, text_color=color.new(color.gray, 50))