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


// === PREVIOUS DAY HIGH/LOW SETTINGS ===
showPrevDayRange = input.bool(true, 'Show Previous Day High/Low', group='Previous Day Range')
prevDayColorHigh = input.color(color.new(color.blue, 0), 'Previous Day High Color', group='Previous Day Range')
prevDayColorLow = input.color(color.new(color.orange, 0), 'Previous Day Low Color', group='Previous Day Range')
prevDayLineWidth = input.int(2, 'Previous Day Line Width', minval=1, maxval=5, group='Previous Day Range')
prevDayLineStyle = input.string('Dashed', 'Previous Day Line Style', options=['Dashed', 'Dotted'], group='Previous Day Range')

// Convert line style string to line.style constant
lineStyle = prevDayLineStyle == 'Dotted' ? line.style_dotted : line.style_dashed

// Get previous day timeframe string or use 'D'
dailyTF = "D"  // fixed daily timeframe

// Request previous daily data (high/low) and previous day start timestamp (session start)
prevDayHigh = request.security(syminfo.tickerid, dailyTF, high[1], lookahead=barmerge.lookahead_on)
prevDayLow = request.security(syminfo.tickerid, dailyTF, low[1], lookahead=barmerge.lookahead_on)
prevDayTime = request.security(syminfo.tickerid, dailyTF, time[1], lookahead=barmerge.lookahead_on)  // Timestamp of previous daily bar start

// Variables to hold lines just once (persistent)
var line prevHighLine = na
var line prevLowLine = na

// Calculate approximate timeframe minutes and future time offset
tf_minutes = timeframe.multiplier * (timeframe.isintraday ? 1 : 1440)
futureBarsCount = 500
futureDeltaMs = tf_minutes * 60000 * futureBarsCount
lineEndTime = time + futureDeltaMs

if showPrevDayRange
    if na(prevHighLine)
        prevHighLine := line.new(x1=prevDayTime, y1=prevDayHigh, x2=lineEndTime, y2=prevDayHigh, xloc=xloc.bar_time, color=prevDayColorHigh, width=prevDayLineWidth, style=lineStyle)
    else
        line.set_xy1(prevHighLine, prevDayTime, prevDayHigh)
        line.set_xy2(prevHighLine, lineEndTime, prevDayHigh)

    if na(prevLowLine)
        prevLowLine := line.new(x1=prevDayTime, y1=prevDayLow, x2=lineEndTime, y2=prevDayLow, xloc=xloc.bar_time, color=prevDayColorLow, width=prevDayLineWidth, style=lineStyle)
    else
        line.set_xy1(prevLowLine, prevDayTime, prevDayLow)
        line.set_xy2(prevLowLine, lineEndTime, prevDayLow)
else
    if not na(prevHighLine)
        line.delete(prevHighLine)
        prevHighLine := na
    if not na(prevLowLine)
        line.delete(prevLowLine)
        prevLowLine := na