ICT HTF MSS & Liquidity (fadi)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © fadizeidan
//

//@version=5
indicator("ICT HTF MSS & Liquidity (fadi)", overlay=true, max_bars_back = 5000, max_lines_count = 500)

//+------------------------------------------------------------------------------------------------------------+//
//+--- Types                                                                                                ---+//
//+------------------------------------------------------------------------------------------------------------+//
type Settings 
    string  level
    string  liquidity_open_style
    string  liquidity_claimed_style
    color   liquidity_open_color
    color   liquidity_claimed_color
    int     liquidity_open_size
    int     liquidity_claimed_size
    int     max_lines
    int     extend

type Pivot
    int     time                    = 0
    float   price                   = 0
    int     time_last               = 0
    bool    claimed                 = false
    bool    isHigh                  = na
    string  lbl_text                = ""
    line    ln                      = na

type MarketStructure
    string          name
    Pivot[]         ST
    Pivot[]         IT
    Pivot[]         LT
    Pivot[]         STH
    Pivot[]         ITH
    Pivot[]         STL
    Pivot[]         ITL
    Settings        settings

type Helper
    string name         = "Helper"

//+------------------------------------------------------------------------------------------------------------+//
//+--- Settings                                                                                             ---+//
//+------------------------------------------------------------------------------------------------------------+//
int MAX_BUFFER                            = 500 // internal maximum node count limit for performance

settings_liquidity                        = "Liquidity"
settings_liquidity_style                  = "Liquidity Style"

Settings Trend_Settings                   = Settings.new()

HTF                                       = input.timeframe("15", "Higher Timeframe", group=settings_liquidity)
Trend_Settings.level                     := input.string("Short Term", "Use", options = ["Short Term", "Intermediate Term", "Long Term"], group=settings_liquidity)
Trend_Settings.liquidity_open_style      := input.string('⎯⎯⎯', 'Open            ', options = ['⎯⎯⎯', '----', '····'], group=settings_liquidity_style, inline="open")
Trend_Settings.liquidity_open_size       := input.int(1, "", options = [1,2,3,4], group=settings_liquidity_style, inline="open")
Trend_Settings.liquidity_open_color      := input.color(color.new(color.purple, 50), '', group=settings_liquidity_style, inline="open")
Trend_Settings.liquidity_claimed_style   := input.string('····', 'Claimed          ', options = ['⎯⎯⎯', '----', '····'], group=settings_liquidity_style, inline="claimed")
Trend_Settings.liquidity_claimed_size    := input.int(1, "", options = [1,2,3,4], group=settings_liquidity_style, inline="claimed")
Trend_Settings.liquidity_claimed_color   := input.color(color.new(color.black, 50), '', group=settings_liquidity_style, inline="claimed")
Trend_Settings.extend                    := input.int(10, title="Extend", minval = 1, group=settings_liquidity_style)
Trend_Settings.max_lines                 := input.int(50, "Number of lines", minval=1, maxval=250, group=settings_liquidity_style)

//+------------------------------------------------------------------------------------------------------------+//
//+--- Variables                                                                                            ---+//
//+------------------------------------------------------------------------------------------------------------+//
color color_transparent             = #ffffff00

Helper helper                       = Helper.new()

var MarketStructure Term            = MarketStructure.new("Term")
var Pivot[] ST_array                = array.new<Pivot>()
var Pivot[] IT_array                = array.new<Pivot>()
var Pivot[] LT_array                = array.new<Pivot>()
var Pivot[] STH_array               = array.new<Pivot>()
var Pivot[] ITH_array               = array.new<Pivot>()
var Pivot[] STL_array               = array.new<Pivot>()
var Pivot[] ITL_array               = array.new<Pivot>()
Term.ST                             := ST_array
Term.IT                             := IT_array
Term.LT                             := LT_array
Term.STH                            := STH_array
Term.ITH                            := ITH_array
Term.STL                            := STL_array
Term.ITL                            := ITL_array
Term.settings                       := Trend_Settings

var float[] highs                   = array.new_float()
var float[] lows                    = array.new_float()
var   int[] times                   = array.new_int()
var  line[] lines                   = array.new_line()

//+------------------------------------------------------------------------------------------------------------+//
//+--- Methods                                                                                              ---+//
//+------------------------------------------------------------------------------------------------------------+//

method Validtimeframe(Helper helper, tf) =>
    helper.name := tf
    n1 = timeframe.in_seconds()
    n2 = timeframe.in_seconds(tf)

    n1 < n2

method LineStyle(Helper helper, string style) =>
    helper.name := style

    out = switch style
        '----' => line.style_dashed
        '····' => line.style_dotted
        => line.style_solid
    
    out

// SkipEQHigh returns the first bar_index of before the equal highs
// This is used to compare pivot points, if two at equal levels
// then we need to compare it to previous high, otherwise it will
// not identify the right STH/STL
method SkipEQHigh(Helper helper, int idx) =>
    helper.name := "Skip EQ Highs"
    i = idx
    while highs.get(i) == highs.get(i-1) and i < 5
        i := i+1
    i
// SkipEQLows returns the first bar_index of before the equal lows
method SkipEQLow(Helper helper, int idx) =>
    helper.name := "Skip EQ Lows"
    i = idx
    while lows.get(i) == lows.get(i-1) and i < 5
        i := i+1
    i

// Same as above, but works on pivot points. it also doesn't care for
// high or low since Pivot is a single price point
method SkipEQPivot(Pivot[] p, int idx) =>
    i = idx
    while p.get(i).price == p.get(i-1).price and p.get(i).lbl_text == p.get(i-1).lbl_text and p.size() < i
        i := i+1
    i

// isHigh returns true if the Pivot point is a high Pivot (STH, ITH, LTH)
method isHigh(Helper helper, string lbl) =>
    helper.name := "isHigh"
    str.endswith(lbl, "H")

//isLow returns true if the Pivot is a low Pivot
method isLow(Helper helper, string lbl) =>
    helper.name := "isLow"
    str.endswith(lbl, "L")

//+------------------------------------------------------------------------------------------------------------+//
//+--- ICT Market Structure Methods                                                                         ---+//
//+------------------------------------------------------------------------------------------------------------+//

// AddLiquidity adds the liquidity lines
method AddLiquidity(MarketStructure MS, Pivot pivot) =>
    if (MS.settings.level == "Short Term" and str.startswith(pivot.lbl_text, "ST")) or (MS.settings.level == "Intermediate Term" and str.startswith(pivot.lbl_text, "IT")) or (MS.settings.level == "Long Term" and str.startswith(pivot.lbl_text, "LT"))
        if na(pivot.ln)
            pivot.ln  := line.new(pivot.time, pivot.price, time+((time-time[1])*MS.settings.extend), pivot.price, xloc=xloc.bar_time, style=helper.LineStyle(MS.settings.liquidity_open_style), color=MS.settings.liquidity_open_color, width=MS.settings.liquidity_open_size)
            lines.unshift(pivot.ln)
            if lines.size() > MS.settings.max_lines
                line l = lines.pop()
                line.delete(l)
        else
            line.set_color(pivot.ln, pivot.claimed ? MS.settings.liquidity_claimed_color : MS.settings.liquidity_open_color)
            line.set_width(pivot.ln, pivot.claimed ? MS.settings.liquidity_claimed_size : MS.settings.liquidity_open_size)
            line.set_x2(pivot.ln, pivot.claimed ? pivot.time_last : time+((time-time[1])*MS.settings.extend))
            line.set_style(pivot.ln, helper.LineStyle(pivot.claimed ? MS.settings.liquidity_claimed_style : MS.settings.liquidity_open_style))
    MS

// FindIT rechecks the Pivot points and renames the Pivot point from ST to IT
method FindIT(MarketStructure MS) =>
    if MS.STH.size() > 2
        h1 = MS.STH.first()
        h2 = MS.STH.get(1)
        h3 = MS.STH.get(MS.STH.SkipEQPivot(2))

        if h2.price > h3.price and h2.price > h1.price and h2.lbl_text == "STH"

            h2.lbl_text := "ITH"
            MS.ITH.unshift(h2)
            MS.AddLiquidity(h2)

    if MS.STL.size() > 2
        l1 = MS.STL.first()
        l2 = MS.STL.get(1)
        l3 = MS.STL.get(MS.STL.SkipEQPivot(2))

        if l2.price < l3.price and l2.price < l1.price and l2.lbl_text == "STL"
            l2.lbl_text := "ITL"
            MS.ITL.unshift(l2)
            MS.AddLiquidity(l2)

    MS

// FindLT rechecks the IT Pivot points and renames the Pivot point from IT to LT
method FindLT(MarketStructure MS) =>
    if MS.ITH.size() > 2

        h1 = MS.ITH.first()
        h2 = MS.ITH.get(1)
        h3 = MS.ITH.get(MS.ITH.SkipEQPivot(2))

        if h2.price > h3.price and h2.price > h1.price and h2.lbl_text == "ITH"
            h2.lbl_text := "LTH"
            MS.LT.unshift(h2)
            MS.AddLiquidity(h2)

    if MS.ITL.size() > 2
        l1 = MS.ITL.first()
        l2 = MS.ITL.get(1)
        l3 = MS.ITL.get(MS.ITL.SkipEQPivot(2))

        if l2.price < l3.price and l2.price < l1.price and l2.lbl_text == "ITL"
            l2.lbl_text := "LTL"
            MS.LT.unshift(l2)
            MS.AddLiquidity(l2)
    MS

// Add method handles the addition of newly discovered ST[H/L] Pivot Point
method Add(MarketStructure MS, float p_price, int p_time, lbl) =>
    bool isNew      = true

    if MS.ST.size() > 0
        Pivot previous = MS.ST.first()
        isNew := previous.time < p_time

    if isNew
        Pivot pivot     = Pivot.new()
        pivot.price     := p_price
        pivot.time      := p_time
        pivot.lbl_text  := lbl
        

        MS.ST.unshift(pivot)
        if lbl == "STH"
            MS.STH.unshift(pivot)
        else
            MS.STL.unshift(pivot)

        MS.AddLiquidity(pivot).FindIT().FindLT()

        if MS.ST.size() > MAX_BUFFER
            Pivot temp = MS.ST.pop()
            line.delete(temp.ln)  
    MS


method findST(MarketStructure MS, float[] h, float[] l, int[] t) =>
    _h = h.get(1) > h.get(helper.SkipEQHigh(2)) and h.get(1) > h.first()
    _l = l.get(1) < l.get(helper.SkipEQLow(2)) and l.get(1) < l.first()

    if _h
        MS.Add(h.get(1), t.get(1), "STH")
    if _l
        MS.Add(l.get(1), t.get(1), "STL")

    MS

// CheckClaimed checks if liquidity has been swept
method CheckClaimed(MarketStructure MS) =>
    if MS.ST.size() > 0
        for i = 0 to MS.ST.size() - 1
            pivot = MS.ST.get(i)
            if not na(pivot.ln)
                if not pivot.claimed
                    if helper.isHigh(pivot.lbl_text)
                        pivot.claimed := high > pivot.price
                    if helper.isLow(pivot.lbl_text)
                        pivot.claimed := low < pivot.price

                    if pivot.claimed
                        pivot.time_last := time
                        pivot.claimed := true
                        line.set_color(pivot.ln, pivot.claimed ? MS.settings.liquidity_claimed_color : MS.settings.liquidity_open_color)
                        line.set_width(pivot.ln, pivot.claimed ? MS.settings.liquidity_claimed_size : MS.settings.liquidity_open_size)
                        line.set_x2(pivot.ln, pivot.claimed ? pivot.time_last : time+((time-time[1])*MS.settings.extend))
                        line.set_style(pivot.ln, helper.LineStyle(pivot.claimed ? MS.settings.liquidity_claimed_style : MS.settings.liquidity_open_style))

                    else
                        line.set_x2(pivot.ln, time+((time-time[1])*MS.settings.extend))
    MS

//+------------------------------------------------------------------------------------------------------------+//
//+--- Main call to start the process                                                                       ---+//
//+------------------------------------------------------------------------------------------------------------+//

[h, h1, h2, h3, h4, h5, l, l1, l2, l3, l4, l5, t, t1]= request.security(syminfo.tickerid, HTF, [high, high[1], high[2], high[3], high[4], high[5], low, low[1], low[2], low[3], low[4], low[5], time, time[1]])
if helper.Validtimeframe(HTF)
    if barstate.isconfirmed 
        array.clear(highs)
        array.clear(lows)
        array.unshift(highs, h5)
        array.unshift(highs, h4)
        array.unshift(highs, h3)
        array.unshift(highs, h2)
        array.unshift(highs, h1)
        array.unshift(highs, h)
        array.unshift(lows, l5)
        array.unshift(lows, l4)
        array.unshift(lows, l3)
        array.unshift(lows, l2)
        array.unshift(lows, l1)
        array.unshift(lows, l)
        array.unshift(times, t1)
        array.unshift(times, t)

        Term.findST(highs, lows, times)//.FindIT().FindLT()

    //if barstate.isconfirmed or barstate.islastconfirmedhistory
        
    Term.CheckClaimed()