//@version=6
indicator('Liquidity Lines & Sweeps + FVG [Lucien]', overlay = true, max_lines_count = 500)

// === LIQUIDITY SETTINGS ===
liquidityBool = input.bool(true, 'Show Liquidity Sweeps', group = 'Liquidity Settings')
liquidity_len = input.int(30, 'Liquidity Length', minval = 5, group = 'Liquidity Settings')
upColor = input.color(color.new(color.teal, 15), title = 'Bullish Liquidity Color', group = 'Liquidity Settings')
downColor = input.color(color.new(color.red, 15), title = 'Bearish Liquidity Color', group = 'Liquidity Settings')

// === EMA SETTINGS ===
showEma = input.bool(true, title="Show EMA", group="Moving Average")
emaLength = input.int(200, title="EMA Length", minval=1, group="Moving Average")
emaSource = input.source(close, title="EMA Source", group="Moving Average")
emaTimeframe = input.timeframe("", title="EMA Timeframe (leave empty for current)", group="Moving Average")
emaColor = input.color(color.new(color.orange, 0), title="EMA Color", group="Moving Average")
emaWidth = input.int(2, title="EMA Line Width", minval=1, maxval=5, group="Moving Average")

// Calculate EMA with timeframe option
emaValue = request.security(syminfo.tickerid, emaTimeframe != "" ? emaTimeframe : timeframe.period, ta.ema(emaSource, emaLength))
plot(showEma ? emaValue : na, title="EMA", color=emaColor, linewidth=emaWidth)

// === FVG SETTINGS ===
isFvgToShow =  input(true, title='Display FVG', group="Fair Value Gap")
bullishFvgColor = input.color(color.new(color.green, 50), 'Bullish FVG Color', group="Fair Value Gap")
bearishFvgColor = input.color(color.new(color.red, 50), 'Bearish FVG Color', group="Fair Value Gap")
mitigatedFvgColor = input.color(color.new(color.gray, 50), 'Mitigated FVG Color', group="Fair Value Gap")
fvgHistoryNbr = input.int(5, 'Number of FVG to show', minval=1, maxval=50, group="Fair Value Gap")
isMitigatedFvgToReduce = input(false, title='Reduce mitigated FVG', group="Fair Value Gap")

// === DATA STRUCTURES ===
// For Liquidity
type liquidity
    float value
    int barStart
    int barEnd
    line liquidityLine
    bool broken
    label sweep

var array<liquidity> bullishLiquidity = array.new<liquidity>()
var array<liquidity> bearishLiquidity = array.new<liquidity>()

// For FVG
var array<box>  fvgBoxes = array.new_box(0)
var array<bool> fvgTypes = array.new_bool(0)
var array<label> fvgLabels = array.new_label(0)
var array<bool> isFvgMitigated = array.new_bool(0)

// === FVG FUNCTIONS ===
FVGDraw(_boxes, _fvgTypes, _isFvgMitigated, _fvgLabels) => 
    for [index, value] in _boxes
        // Processing bullish FVG
        if(array.get(_fvgTypes, index))
            if(low <= box.get_bottom(value))
                array.remove(_boxes, index)
                array.remove(_fvgTypes, index)
                array.remove(_isFvgMitigated, index)
                label.delete(array.get(_fvgLabels, index))
                array.remove(_fvgLabels, index)
                box.delete(value)
            else 
                if(low < box.get_top((value)))
                    box.set_bgcolor(value, mitigatedFvgColor)    
                    if(not(array.get(_isFvgMitigated, index)))
                        alert("FVG has been mitigated", alert.freq_once_per_bar)
                        array.set(_isFvgMitigated, index, true)
                    if(isMitigatedFvgToReduce)
                        box.set_top(value, low)
                box.set_right(value, bar_index)
                label.set_x(array.get(_fvgLabels, index), (box.get_left(value) + box.get_right(value)) / 2 )
                label.set_y(array.get(_fvgLabels, index), box.get_top(value) - (box.get_top(value) - box.get_bottom(value)) / 2)
        // Processing bearish FVG
        else
            if(high >= box.get_top(value))
                array.remove(_boxes, index)
                array.remove(_fvgTypes, index)
                array.remove(_isFvgMitigated, index)
                label.delete(array.get(_fvgLabels, index))
                array.remove(_fvgLabels, index)
                box.delete(value)
            else
                if(high > box.get_bottom((value)))
                    box.set_bgcolor(value, mitigatedFvgColor)    
                    if(not(array.get(_isFvgMitigated, index)))
                        alert("FVG has been mitigated", alert.freq_once_per_bar)
                        array.set(_isFvgMitigated, index, true)
                    if(isMitigatedFvgToReduce)
                        box.set_bottom(value, high)
                box.set_right(value, bar_index)
                label.set_x(array.get(_fvgLabels, index), (box.get_left(value) + box.get_right(value)) / 2 )
                label.set_y(array.get(_fvgLabels, index), box.get_top(value) - (box.get_top(value) - box.get_bottom(value)) / 2)

// === LIQUIDITY DETECTION ===
phLiquidity = ta.pivothigh(high, liquidity_len, liquidity_len)
plLiquidity = ta.pivotlow(low, liquidity_len, liquidity_len)

if not na(phLiquidity) and liquidityBool
    liquidity newLiquidity = liquidity.new()
    newLiquidity.value := high[liquidity_len]
    newLiquidity.barStart := time[liquidity_len]
    newLiquidity.barEnd := time
    newLiquidity.broken := false
    newLiquidity.liquidityLine := line.new(x1 = newLiquidity.barStart, y1 = newLiquidity.value, x2 = newLiquidity.barEnd, y2 = newLiquidity.value, xloc = xloc.bar_time, color = downColor, width = 1)
    array.push(bearishLiquidity, newLiquidity)
    if array.size(bearishLiquidity) > 7
        deletedLiquidity = array.shift(bearishLiquidity)
        deletedLiquidity.liquidityLine.delete()

if not na(plLiquidity) and liquidityBool
    liquidity newLiquidity = liquidity.new()
    newLiquidity.value := low[liquidity_len]
    newLiquidity.barStart := time[liquidity_len]
    newLiquidity.barEnd := time
    newLiquidity.broken := false
    newLiquidity.liquidityLine := line.new(x1 = newLiquidity.barStart, y1 = newLiquidity.value, x2 = newLiquidity.barEnd, y2 = newLiquidity.value, xloc = xloc.bar_time, color = upColor, width = 1)
    array.push(bullishLiquidity, newLiquidity)
    if array.size(bullishLiquidity) > 7
        deletedLiquidity = array.shift(bullishLiquidity)
        deletedLiquidity.liquidityLine.delete()

// === FVG DETECTION ===
isBullishFVG = high[3] < low[1]
isBearishFVG = low[3] > high[1]

if(isBullishFVG and isFvgToShow)
    box _box = box.new(left=bar_index - 2, top=low[1], right=bar_index[1], bottom=high[3], border_style=line.style_solid, border_width=1, bgcolor=bullishFvgColor, border_color=color.new(color.green, 100))
    label _label = label.new(math.ceil((_box.get_left() + _box.get_right()) / 2), _box.get_top() - (_box.get_top() - _box.get_bottom()) / 2, text = "FVG", style = label.style_none, textcolor = color.white)     
    array.push(fvgBoxes, _box)
    array.push(fvgTypes, true)
    array.push(isFvgMitigated, false)
    array.push(fvgLabels, _label)

    if(array.size(fvgBoxes) > fvgHistoryNbr + 1)
        box.delete(array.get(fvgBoxes, 0))
        label.delete(array.get(fvgLabels, 0))
        array.remove(fvgLabels, 0)
        array.remove(fvgBoxes, 0)
        array.remove(fvgTypes, 0)
        array.remove(isFvgMitigated, 0)

if(isBearishFVG and isFvgToShow)
    box _box = box.new(left=bar_index - 2, top=low[3], right=bar_index[1], bottom=high[1], border_style=line.style_solid, border_width=1, bgcolor=bearishFvgColor, border_color=color.new(color.red, 100))
    label _label = label.new(math.ceil((_box.get_left() + _box.get_right()) / 2), _box.get_top() - (_box.get_top() - _box.get_bottom()) / 2, text = "FVG", style = label.style_none, textcolor = color.white)     
    array.push(fvgBoxes, _box)
    array.push(fvgTypes, false)
    array.push(isFvgMitigated, false)
    array.push(fvgLabels, _label)

    if(array.size(fvgBoxes) > fvgHistoryNbr + 1)
        box.delete(array.get(fvgBoxes, 0))
        label.delete(array.get(fvgLabels, 0))
        array.remove(fvgLabels, 0)
        array.remove(fvgBoxes, 0)
        array.remove(fvgTypes, 0)
        array.remove(isFvgMitigated, 0)

// === UPDATE VISUALS ===
// Update Liquidity Lines and Sweeps
var bool sweepAlert = false

if array.size(bearishLiquidity) > 0
    liquidity testLiquidity = na
    for i = array.size(bearishLiquidity) - 1 to 0 by 1
        testLiquidity := array.get(bearishLiquidity, i)
        if high > testLiquidity.value
            testLiquidity.liquidityLine.set_x2(time)
            testLiquidity.liquidityLine.set_style(line.style_dashed)
            array.remove(bearishLiquidity, i)
            if close < testLiquidity.value
                testLiquidity.sweep := label.new(x = time, y = high, text = 'x', xloc = xloc.bar_time, style = label.style_label_down, size = size.normal, textcolor = color.new(color.purple, 0), color = color.new(color.white, 100))
                sweepAlert := true
        else
            testLiquidity.liquidityLine.set_x2(time)

if array.size(bullishLiquidity) > 0
    liquidity testLiquidity = na
    for i = array.size(bullishLiquidity) - 1 to 0 by 1
        testLiquidity := array.get(bullishLiquidity, i)
        if low < testLiquidity.value
            testLiquidity.liquidityLine.set_x2(time)
            testLiquidity.liquidityLine.set_style(line.style_dashed)
            array.remove(bullishLiquidity, i)
            if close > testLiquidity.value
                testLiquidity.sweep := label.new(x = time, y = low, text = 'x', xloc = xloc.bar_time, style = label.style_label_up, size = size.normal, textcolor = color.new(color.teal, 0), color = color.new(color.white, 100))
                sweepAlert := true
        else
            testLiquidity.liquidityLine.set_x2(time)

// Update FVGs
FVGDraw(fvgBoxes, fvgTypes, isFvgMitigated, fvgLabels)

// === ALERTS ===
alertcondition(sweepAlert, title = "Liquidity Sweep", message = "A liquidity zone has been swept.")
sweepAlert := false