// ╔══════════════════════════════════════════════════════════════╗
// ║        CNPS Universal Bot Template — Pine Script v6          ║
// ║  Instrument: VN30! | M15 SMC OB+FVG / M1 Strict Confirm     ║
// ║  Version: CNPS SMC STRICT v5 — BOS Structural SL            ║
// ║  Recommended timeframe: M1 | Slippage = 3                   ║
// ╚══════════════════════════════════════════════════════════════╝
//*The cost to open a position in futures trading is roughly VND 30,000,000 per contract.
//For VN Future Trading, the commission value is set at VND 10,000 per contract traded. Tax is not yet included.

//@version=6
strategy(
     title="CNPS 11 SMC",
     shorttitle="CNPS 11 SMC",
     overlay=true,
     initial_capital=100000000,
     default_qty_type=strategy.fixed,
     default_qty_value=3,
     margin_long=0,
     margin_short=0,
     slippage=3,
     commission_type=strategy.commission.cash_per_contract,
     commission_value=10000,
     fill_orders_on_standard_ohlc=true,
     max_boxes_count=500,
     max_labels_count=500,
     max_lines_count=500,
     max_bars_back=1500
)


// ════════════════════════════════════════════════════════════════
// SECTION 1: INPUTS
// ════════════════════════════════════════════════════════════════

// ── M15 Structure ───────────────────────────────────────────────
htfTf = input.timeframe(
     "15",
     title="Higher Timeframe",
     group="M15 Structure"
)

m15StructureLookback = input.int(
     10,
     title="M15 Structure Lookback",
     minval=3,
     maxval=50,
     group="M15 Structure"
)

structureBreakMode = input.string(
     "Candle Close",
     title="Structure Break Mode",
     options=["Candle Close", "Wick"],
     group="M15 Structure"
)


// ── M1 Confirmation ─────────────────────────────────────────────
m1PivotLength = input.int(
     2,
     title="M1 Micro Pivot Length",
     minval=1,
     maxval=10,
     group="M1 Confirmation"
)

confirmWindowBars = input.int(
     60,
     title="Confirmation Window Bars",
     minval=10,
     maxval=300,
     group="M1 Confirmation"
)

emaLen = input.int(
     9,
     title="EMA Length",
     minval=1,
     group="M1 Confirmation"
)


// ── Zone Settings ───────────────────────────────────────────────
useOrderBlock = input.bool(
     true,
     title="Use Order Block?",
     group="Zone Settings"
)

useFVG = input.bool(
     true,
     title="Use Fair Value Gap?",
     group="Zone Settings"
)

obSearchLookback = input.int(
     12,
     title="Order Block Search Lookback",
     minval=3,
     maxval=50,
     group="Zone Settings"
)

maxZonesPerType = input.int(
     50,
     title="Maximum Historical Zones Per Type",
     minval=5,
     maxval=100,
     group="Zone Settings"
)


// ── Stop Loss ───────────────────────────────────────────────────
slBuffer = input.float(
     0.5,
     title="M1 Structural Stop Buffer Points",
     minval=0.0,
     step=0.1,
     group="Stop Loss"
)


// ── Trade Direction ─────────────────────────────────────────────
tradeDir = input.string(
     "Both",
     title="Trade Direction",
     options=["Long", "Short", "Both"],
     group="Trade Direction"
)


// ── Time Filter ─────────────────────────────────────────────────
useTimeFilter = input.bool(
     true,
     title="Use Time Filter?",
     group="Time Filter"
)

startHour = input.int(
     9,
     title="Start Hour",
     minval=0,
     maxval=23,
     group="Time Filter"
)

startMinute = input.int(
     0,
     title="Start Minute",
     minval=0,
     maxval=59,
     group="Time Filter"
)

endHour = input.int(
     14,
     title="End Hour",
     minval=0,
     maxval=23,
     group="Time Filter"
)

endMinute = input.int(
     30,
     title="End Minute",
     minval=0,
     maxval=59,
     group="Time Filter"
)


// ── Alert Control ───────────────────────────────────────────────
disableSLAlerts = input.bool(
     true,
     title="Disable Stop Loss Alerts? AmiX Safe Mode",
     group="Alert Control"
)

disableStructureExitAlerts = input.bool(
     false,
     title="Disable Structure Exit Alerts?",
     group="Alert Control"
)


// ── Display ─────────────────────────────────────────────────────
showZones = input.bool(
     true,
     title="Show OB / FVG Zones?",
     group="Display"
)

showZoneLabels = input.bool(
     false,
     title="Show Zone Text?",
     group="Display"
)

showEMA = input.bool(
     true,
     title="Show EMA 9?",
     group="Display"
)

showM15Structure = input.bool(
     true,
     title="Show M15 BOS / CHoCH?",
     group="Display"
)

showM1ConfirmStructure = input.bool(
     true,
     title="Show M1 Entry BOS / CHoCH?",
     group="Display"
)

showSignalShapes = input.bool(
     true,
     title="Show Entry / Exit Markers?",
     group="Display"
)

showSetupShapes = input.bool(
     false,
     title="Show Setup Debug Markers?",
     group="Display"
)

showPendingSL = input.bool(
     false,
     title="Show Pending Structural SL?",
     group="Display"
)

structureHistory = input.int(
     100,
     title="Structure Lines To Keep",
     minval=5,
     maxval=200,
     group="Display"
)

bosColor = input.color(
     color.new(color.gray, 0),
     title="BOS Color",
     group="Display"
)

chochColor = input.color(
     color.new(color.orange, 0),
     title="CHoCH Color",
     group="Display"
)

bullOBColor = input.color(
     color.new(color.green, 84),
     title="Bullish OB Color",
     group="Display"
)

bearOBColor = input.color(
     color.new(color.red, 84),
     title="Bearish OB Color",
     group="Display"
)

bullFVGColor = input.color(
     color.new(color.aqua, 88),
     title="Bullish FVG Color",
     group="Display"
)

bearFVGColor = input.color(
     color.new(color.orange, 88),
     title="Bearish FVG Color",
     group="Display"
)


// ════════════════════════════════════════════════════════════════
// SECTION 2: INDICATORS
// ════════════════════════════════════════════════════════════════

currentTfSeconds = timeframe.in_seconds(timeframe.period)
htfSeconds = timeframe.in_seconds(htfTf)

isM1Chart = currentTfSeconds == 60
isM15Chart = currentTfSeconds == htfSeconds

useBodyBreak = structureBreakMode == "Candle Close"


// ── Structure High Helper ───────────────────────────────────────
get_structure_highest_bar(int lookback) =>
    int idx = 0

    int maxBar = bar_index > lookback
         ? ta.highestbars(high, lookback)
         : ta.highestbars(high, bar_index + 1)

    for i = 0 to lookback - 1
        if bar_index >= i + 2
            bool localHigh = high[i + 1] > high[i + 2] and high[i] <= high[i + 1]

            if localHigh and ((i + 1) * -1) >= maxBar
                idx := (i + 1) * -1

    int result = idx == 0 ? maxBar : idx
    result


// ── Structure Low Helper ────────────────────────────────────────
get_structure_lowest_bar(int lookback) =>
    int idx = 0

    int minBar = bar_index > lookback
         ? ta.lowestbars(low, lookback)
         : ta.lowestbars(low, bar_index + 1)

    for i = 0 to lookback - 1
        if bar_index >= i + 2
            bool localLow = low[i + 1] < low[i + 2] and low[i] >= low[i + 1]

            if localLow and ((i + 1) * -1) >= minBar
                idx := (i + 1) * -1

    int result = idx == 0 ? minBar : idx
    result


// ── Raw SMC Structure Engine ────────────────────────────────────
f_structure(int lookback, bool bodyBreak) =>
    var float structureHigh = na
    var float structureLow = na

    var int structureHighStartIndex = na
    var int structureLowStartIndex = na

    var int structureHighStartTime = na
    var int structureLowStartTime = na

    var int structureDirection = 0

    bool bullBOS = false
    bool bearBOS = false

    bool bullCHoCH = false
    bool bearCHoCH = false

    int bullEventStartTime = na
    int bullEventEndTime = na
    float bullEventLevel = na

    int bearEventStartTime = na
    int bearEventEndTime = na
    float bearEventLevel = na


    if na(structureHigh)
        structureHigh := high
        structureLow := low

        structureHighStartIndex := bar_index
        structureLowStartIndex := bar_index

        structureHighStartTime := time
        structureLowStartTime := time

        structureDirection := 0


    int structureMaxBar = bar_index + get_structure_highest_bar(lookback)
    int structureMinBar = bar_index + get_structure_lowest_bar(lookback)

    float lowBreakPrice = bodyBreak ? close : low
    float highBreakPrice = bodyBreak ? close : high

    bool enoughBars = bar_index > 3

    bool normalLowBreak = false
    bool normalHighBreak = false


    if enoughBars
        normalLowBreak := (
             lowBreakPrice < structureLow and
             lowBreakPrice[1] >= structureLow and
             lowBreakPrice[2] >= structureLow and
             lowBreakPrice[3] >= structureLow and
             bar_index[1] > structureLowStartIndex and
             bar_index[2] > structureLowStartIndex and
             bar_index[3] > structureLowStartIndex
        )

        normalHighBreak := (
             highBreakPrice > structureHigh and
             highBreakPrice[1] <= structureHigh and
             highBreakPrice[2] <= structureHigh and
             highBreakPrice[3] <= structureHigh and
             bar_index[1] > structureHighStartIndex and
             bar_index[2] > structureHighStartIndex and
             bar_index[3] > structureHighStartIndex
        )


    bool continuationLowBreak = structureDirection == 2 and lowBreakPrice < structureLow
    bool continuationHighBreak = structureDirection == 1 and highBreakPrice > structureHigh

    bool structureLowBroken = normalLowBreak or continuationLowBreak
    bool structureHighBroken = normalHighBreak or continuationHighBreak


    if structureLowBroken
        bearEventStartTime := structureLowStartTime
        bearEventEndTime := time_close
        bearEventLevel := structureLow

        if structureDirection == 1
            bearBOS := true
        else
            bearCHoCH := true

        structureDirection := 1

        structureHighStartIndex := structureMaxBar
        structureLowStartIndex := bar_index

        int highOffset = math.max(
             0,
             math.min(
                 lookback,
                 bar_index - structureHighStartIndex
             )
        )

        structureHigh := high[highOffset]
        structureLow := low

        structureHighStartTime := time[highOffset]
        structureLowStartTime := time


    else if structureHighBroken
        bullEventStartTime := structureHighStartTime
        bullEventEndTime := time_close
        bullEventLevel := structureHigh

        if structureDirection == 2
            bullBOS := true
        else
            bullCHoCH := true

        structureDirection := 2

        structureHighStartIndex := bar_index
        structureLowStartIndex := structureMinBar

        int lowOffset = math.max(
             0,
             math.min(
                 lookback,
                 bar_index - structureLowStartIndex
             )
        )

        structureHigh := high
        structureLow := low[lowOffset]

        structureHighStartTime := time
        structureLowStartTime := time[lowOffset]


    else
        if high > structureHigh and (structureDirection == 0 or structureDirection == 2)
            bool allowHighUpdate = true

            if bodyBreak and enoughBars
                bool enoughBarsAfterHigh = (
                     bar_index[1] > structureHighStartIndex and
                     bar_index[2] > structureHighStartIndex and
                     bar_index[3] > structureHighStartIndex
                )

                allowHighUpdate := not enoughBarsAfterHigh

            if allowHighUpdate
                structureHigh := high
                structureHighStartIndex := bar_index
                structureHighStartTime := time


        else if low < structureLow and (structureDirection == 0 or structureDirection == 1)
            bool allowLowUpdate = true

            if bodyBreak and enoughBars
                bool enoughBarsAfterLow = (
                     bar_index[1] > structureLowStartIndex and
                     bar_index[2] > structureLowStartIndex and
                     bar_index[3] > structureLowStartIndex
                )

                allowLowUpdate := not enoughBarsAfterLow

            if allowLowUpdate
                structureLow := low
                structureLowStartIndex := bar_index
                structureLowStartTime := time


    [
         structureDirection,
         bullBOS,
         bearBOS,
         bullCHoCH,
         bearCHoCH,
         structureHigh,
         structureLow,
         bullEventStartTime,
         bullEventEndTime,
         bullEventLevel,
         bearEventStartTime,
         bearEventEndTime,
         bearEventLevel
    ]


// ── Bullish OB Finder ───────────────────────────────────────────
f_find_bullish_ob(int lookback) =>
    float obTop = na
    float obBottom = na
    int obTime = na

    for i = 3 to lookback + 3
        if na(obTop)
            bool bearishCandle = close[i] < open[i]

            if bearishCandle
                obTop := high[i]
                obBottom := low[i]
                obTime := time[i]

    [
         obTop,
         obBottom,
         obTime
    ]


// ── Bearish OB Finder ───────────────────────────────────────────
f_find_bearish_ob(int lookback) =>
    float obTop = na
    float obBottom = na
    int obTime = na

    for i = 3 to lookback + 3
        if na(obTop)
            bool bullishCandle = close[i] > open[i]

            if bullishCandle
                obTop := high[i]
                obBottom := low[i]
                obTime := time[i]

    [
         obTop,
         obBottom,
         obTime
    ]


// ── M15 FVG + OB Event Engine ───────────────────────────────────
f_m15_zone_event(int obLookback) =>
    bool bullishFVG = high[3] < low[1]
    bool bearishFVG = low[3] > high[1]

    int bullEventTime = na
    int bearEventTime = na

    int bullFVGStartTime = na
    int bearFVGStartTime = na

    float bullFVGTop = na
    float bullFVGBottom = na

    float bearFVGTop = na
    float bearFVGBottom = na

    float bullOBTop = na
    float bullOBBottom = na
    int bullOBTime = na

    float bearOBTop = na
    float bearOBBottom = na
    int bearOBTime = na


    if bullishFVG
        bullEventTime := time_close

        bullFVGStartTime := time[2]
        bullFVGTop := low[1]
        bullFVGBottom := high[3]

        [
             foundBullOBTop,
             foundBullOBBottom,
             foundBullOBTime
        ] = f_find_bullish_ob(obLookback)

        bullOBTop := foundBullOBTop
        bullOBBottom := foundBullOBBottom
        bullOBTime := foundBullOBTime


    if bearishFVG
        bearEventTime := time_close

        bearFVGStartTime := time[2]
        bearFVGTop := low[3]
        bearFVGBottom := high[1]

        [
             foundBearOBTop,
             foundBearOBBottom,
             foundBearOBTime
        ] = f_find_bearish_ob(obLookback)

        bearOBTop := foundBearOBTop
        bearOBBottom := foundBearOBBottom
        bearOBTime := foundBearOBTime


    [
         bullEventTime,
         bullFVGStartTime,
         bullFVGTop,
         bullFVGBottom,
         bullOBTime,
         bullOBTop,
         bullOBBottom,
         bearEventTime,
         bearFVGStartTime,
         bearFVGTop,
         bearFVGBottom,
         bearOBTime,
         bearOBTop,
         bearOBBottom
    ]


// ── Highest Price Between M1 Bars ───────────────────────────────
f_highest_between(int startBar, int endBar) =>
    float resultPrice = na
    int resultTime = na

    if not na(startBar) and not na(endBar) and endBar >= startBar
        for i = 0 to 350
            if bar_index >= i
                int candidateBar = bar_index - i

                bool insideRange = candidateBar >= startBar and candidateBar <= endBar

                if insideRange
                    if na(resultPrice) or high[i] > resultPrice
                        resultPrice := high[i]
                        resultTime := time[i]

    [
         resultPrice,
         resultTime
    ]


// ── Lowest Price Between M1 Bars ────────────────────────────────
f_lowest_between(int startBar, int endBar) =>
    float resultPrice = na
    int resultTime = na

    if not na(startBar) and not na(endBar) and endBar >= startBar
        for i = 0 to 350
            if bar_index >= i
                int candidateBar = bar_index - i

                bool insideRange = candidateBar >= startBar and candidateBar <= endBar

                if insideRange
                    if na(resultPrice) or low[i] < resultPrice
                        resultPrice := low[i]
                        resultTime := time[i]

    [
         resultPrice,
         resultTime
    ]


// ════════════════════════════════════════════════════════════════
// M15 STRUCTURE DATA
// ════════════════════════════════════════════════════════════════

[
     m15RawDirection,
     m15BullBOS,
     m15BearBOS,
     m15BullCHoCH,
     m15BearCHoCH,
     m15StructureHigh,
     m15StructureLow,
     m15BullEventStartTime,
     m15BullEventEndTime,
     m15BullEventLevel,
     m15BearEventStartTime,
     m15BearEventEndTime,
     m15BearEventLevel
] = request.security(
     syminfo.tickerid,
     htfTf,
     f_structure(
         m15StructureLookback,
         useBodyBreak
     ),
     barmerge.gaps_off,
     barmerge.lookahead_off
)


// ── Unique M15 Structure Events ─────────────────────────────────
bool m15BullEventOnce = (
     not na(m15BullEventEndTime) and
     (
         na(m15BullEventEndTime[1]) or
         m15BullEventEndTime != m15BullEventEndTime[1]
     )
)

bool m15BearEventOnce = (
     not na(m15BearEventEndTime) and
     (
         na(m15BearEventEndTime[1]) or
         m15BearEventEndTime != m15BearEventEndTime[1]
     )
)

bool m15BullCHoCHOnce = m15BullEventOnce and m15BullCHoCH
bool m15BearCHoCHOnce = m15BearEventOnce and m15BearCHoCH

bool m15BullBOSOnce = m15BullEventOnce and m15BullBOS
bool m15BearBOSOnce = m15BearEventOnce and m15BearBOS


// ════════════════════════════════════════════════════════════════
// STRICT M15 CONFIRMED TREND
// ════════════════════════════════════════════════════════════════
//
// CHoCH alone = pending reversal.
//
// Trend changes only after:
// CHoCH → BOS in same direction.
//
var int confirmedM15Trend = 0
var int pendingM15Trend = 0

int confirmedTrendBeforeUpdate = confirmedM15Trend


if m15BullCHoCHOnce
    pendingM15Trend := 1


if m15BearCHoCHOnce
    pendingM15Trend := -1


if m15BullBOSOnce
    if pendingM15Trend == 1
        confirmedM15Trend := 1
        pendingM15Trend := 0

    else if confirmedM15Trend == 1
        pendingM15Trend := 0


if m15BearBOSOnce
    if pendingM15Trend == -1
        confirmedM15Trend := -1
        pendingM15Trend := 0

    else if confirmedM15Trend == -1
        pendingM15Trend := 0


bool m15BullConfirmed = confirmedM15Trend == 1
bool m15BearConfirmed = confirmedM15Trend == -1

bool m15BullTrendFlip = confirmedM15Trend == 1 and confirmedTrendBeforeUpdate != 1
bool m15BearTrendFlip = confirmedM15Trend == -1 and confirmedTrendBeforeUpdate != -1


// ════════════════════════════════════════════════════════════════
// M15 ZONE DATA
// ════════════════════════════════════════════════════════════════

[
     m15BullZoneEventTime,
     m15BullFVGStartTime,
     m15BullFVGTop,
     m15BullFVGBottom,
     m15BullOBTime,
     m15BullOBTop,
     m15BullOBBottom,
     m15BearZoneEventTime,
     m15BearFVGStartTime,
     m15BearFVGTop,
     m15BearFVGBottom,
     m15BearOBTime,
     m15BearOBTop,
     m15BearOBBottom
] = request.security(
     syminfo.tickerid,
     htfTf,
     f_m15_zone_event(
         obSearchLookback
     ),
     barmerge.gaps_off,
     barmerge.lookahead_off
)


bool newBullZoneEvent = (
     not na(m15BullZoneEventTime) and
     (
         na(m15BullZoneEventTime[1]) or
         m15BullZoneEventTime != m15BullZoneEventTime[1]
     )
)


bool newBearZoneEvent = (
     not na(m15BearZoneEventTime) and
     (
         na(m15BearZoneEventTime[1]) or
         m15BearZoneEventTime != m15BearZoneEventTime[1]
     )
)


// ── M1 Micro Pivots ─────────────────────────────────────────────
m1PivotHigh = ta.pivothigh(
     high,
     m1PivotLength,
     m1PivotLength
)

m1PivotLow = ta.pivotlow(
     low,
     m1PivotLength,
     m1PivotLength
)

int m1PivotHighBar = bar_index - m1PivotLength
int m1PivotLowBar = bar_index - m1PivotLength

int m1PivotHighTime = time[m1PivotLength]
int m1PivotLowTime = time[m1PivotLength]


float lastConfirmedPivotHigh = ta.valuewhen(
     not na(m1PivotHigh),
     m1PivotHigh,
     0
)

float lastConfirmedPivotLow = ta.valuewhen(
     not na(m1PivotLow),
     m1PivotLow,
     0
)

int lastConfirmedPivotHighBar = ta.valuewhen(
     not na(m1PivotHigh),
     m1PivotHighBar,
     0
)

int lastConfirmedPivotLowBar = ta.valuewhen(
     not na(m1PivotLow),
     m1PivotLowBar,
     0
)

int lastConfirmedPivotHighTime = ta.valuewhen(
     not na(m1PivotHigh),
     m1PivotHighTime,
     0
)

int lastConfirmedPivotLowTime = ta.valuewhen(
     not na(m1PivotLow),
     m1PivotLowTime,
     0
)


// ── EMA 9 ───────────────────────────────────────────────────────
ema9 = ta.ema(
     close,
     emaLen
)


// ── Global M1 Structure: Position Exit Only ─────────────────────
[
     globalM1Direction,
     globalM1BullBOS,
     globalM1BearBOS,
     globalM1BullCHoCH,
     globalM1BearCHoCH,
     globalM1StructureHigh,
     globalM1StructureLow,
     globalM1BullStart,
     globalM1BullEnd,
     globalM1BullLevel,
     globalM1BearStart,
     globalM1BearEnd,
     globalM1BearLevel
] = f_structure(
     10,
     useBodyBreak
)


// ════════════════════════════════════════════════════════════════
// SECTION 3: STRATEGY RATIONALE
// ════════════════════════════════════════════════════════════════

// M15:
//
// Opposite CHoCH
// → pending reversal.
//
// CHoCH alone does NOT flip confirmed trend.
//
// CHoCH + BOS in new direction
// → confirmed trend flips.
//
// Confirmed Bull:
// only Bull OB + Bull FVG are created.
//
// Confirmed Bear:
// only Bear OB + Bear FVG are created.
//
// Zone FIRST TOUCH:
// → consume the zone
// → arm M1 confirmation.
//
// A new zone appearing later:
// → does NOT reset the active M1 setup.
//
// Another zone being touched while a setup is active:
// → does NOT reset the setup.
//
// LONG:
//
// Bull zone touch
// → M1 Bull CHoCH
// → Pullback Pivot Low
// → Bull BOS
// → BOS confirmation candle CLOSE > EMA9
// → LONG
// → SL below the Pullback Pivot Low.
//
// SHORT:
//
// Bear zone touch
// → M1 Bear CHoCH
// → Pullback Pivot High
// → Bear BOS
// → BOS confirmation candle CLOSE < EMA9
// → SHORT
// → SL above the Pullback Pivot High.
//
// OB / FVG no longer determines Stop Loss.
//
// No fixed Take Profit.
// Opposite M1 structure closes the position.


// ════════════════════════════════════════════════════════════════
// SECTION 4: SIGNAL LOGIC
// ════════════════════════════════════════════════════════════════


// ────────────────────────────────────────────────────────────────
// STRUCTURE DRAWING
// ────────────────────────────────────────────────────────────────

var array<line> m15StructureLines = array.new_line()
var array<label> m15StructureLabels = array.new_label()

var array<line> m1ConfirmLines = array.new_line()
var array<label> m1ConfirmLabels = array.new_label()


f_draw_structure(
     array<line> lineArray,
     array<label> labelArray,
     int startTime,
     int endTime,
     float level,
     string structureText,
     color structureColor
) =>

    if not na(startTime) and not na(endTime) and not na(level)

        line newLine = line.new(
             x1=startTime,
             y1=level,
             x2=endTime,
             y2=level,
             xloc=xloc.bar_time,
             extend=extend.none,
             color=structureColor,
             width=1
        )

        int middleTime = startTime + int((endTime - startTime) / 2)

        label newLabel = label.new(
             x=middleTime,
             y=level,
             xloc=xloc.bar_time,
             text=structureText,
             style=label.style_none,
             textcolor=structureColor,
             size=size.small
        )

        array.push(
             lineArray,
             newLine
        )

        array.push(
             labelArray,
             newLabel
        )

        if array.size(lineArray) > structureHistory

            line oldestLine = array.shift(
                 lineArray
            )

            label oldestLabel = array.shift(
                 labelArray
            )

            if not na(oldestLine)
                line.delete(
                     oldestLine
                )

            if not na(oldestLabel)
                label.delete(
                     oldestLabel
                )

    0


// ── M15 Structure: M15 Chart Only ───────────────────────────────
if isM15Chart and showM15Structure and m15BullEventOnce

    string bullStructureText = m15BullCHoCH ? "CHoCH" : "BOS"
    color bullStructureColor = m15BullCHoCH ? chochColor : bosColor

    f_draw_structure(
         m15StructureLines,
         m15StructureLabels,
         m15BullEventStartTime,
         m15BullEventEndTime,
         m15BullEventLevel,
         bullStructureText,
         bullStructureColor
    )


if isM15Chart and showM15Structure and m15BearEventOnce

    string bearStructureText = m15BearCHoCH ? "CHoCH" : "BOS"
    color bearStructureColor = m15BearCHoCH ? chochColor : bosColor

    f_draw_structure(
         m15StructureLines,
         m15StructureLabels,
         m15BearEventStartTime,
         m15BearEventEndTime,
         m15BearEventLevel,
         bearStructureText,
         bearStructureColor
    )


// ════════════════════════════════════════════════════════════════
// ZONE STORAGE
// ════════════════════════════════════════════════════════════════

// Bull OB
var array<box> bullOBBoxes = array.new_box()
var array<float> bullOBTops = array.new_float()
var array<float> bullOBBottoms = array.new_float()
var array<bool> bullOBActive = array.new_bool()
var array<int> bullOBBirthTimes = array.new_int()

// Bull FVG
var array<box> bullFVGBoxes = array.new_box()
var array<float> bullFVGTops = array.new_float()
var array<float> bullFVGBottoms = array.new_float()
var array<bool> bullFVGActive = array.new_bool()
var array<int> bullFVGBirthTimes = array.new_int()

// Bear OB
var array<box> bearOBBoxes = array.new_box()
var array<float> bearOBTops = array.new_float()
var array<float> bearOBBottoms = array.new_float()
var array<bool> bearOBActive = array.new_bool()
var array<int> bearOBBirthTimes = array.new_int()

// Bear FVG
var array<box> bearFVGBoxes = array.new_box()
var array<float> bearFVGTops = array.new_float()
var array<float> bearFVGBottoms = array.new_float()
var array<bool> bearFVGActive = array.new_bool()
var array<int> bearFVGBirthTimes = array.new_int()


// ── Create Zone ─────────────────────────────────────────────────
f_create_zone(
     int zoneStartTime,
     float zoneTop,
     float zoneBottom,
     color zoneColor,
     color borderColor,
     string zoneName
) =>

    float actualTop = math.max(
         zoneTop,
         zoneBottom
    )

    float actualBottom = math.min(
         zoneTop,
         zoneBottom
    )

    box result = na

    if showZones and not na(zoneStartTime)

        result := box.new(
             left=zoneStartTime,
             top=actualTop,
             right=time,
             bottom=actualBottom,
             xloc=xloc.bar_time,
             extend=extend.right,
             bgcolor=zoneColor,
             border_color=borderColor,
             border_width=1,
             text=showZoneLabels ? zoneName : "",
             text_color=color.new(color.white, 30),
             text_size=size.tiny
        )

    result


// ── Stop Zone ───────────────────────────────────────────────────
f_stop_zone(
     box zoneBox
) =>

    if not na(zoneBox)

        box.set_extend(
             zoneBox,
             extend.none
        )

        box.set_right(
             zoneBox,
             time
        )

    0


// ── Stop All Active Zones ───────────────────────────────────────
f_stop_all_active(
     array<box> boxes,
     array<bool> activeArray
) =>

    int count = array.size(
         boxes
    )

    if count > 0

        for i = 0 to count - 1

            bool active = array.get(
                 activeArray,
                 i
            )

            if active

                box currentBox = array.get(
                     boxes,
                     i
                )

                f_stop_zone(
                     currentBox
                )

                array.set(
                     activeArray,
                     i,
                     false
                )

    0


// ── Trim Zone History ───────────────────────────────────────────
f_trim_zone_history(
     array<box> boxes,
     array<float> tops,
     array<float> bottoms,
     array<bool> activeArray,
     array<int> birthTimes
) =>

    if array.size(boxes) > maxZonesPerType

        box oldestBox = array.shift(
             boxes
        )

        if not na(oldestBox)

            box.delete(
                 oldestBox
            )

        array.shift(
             tops
        )

        array.shift(
             bottoms
        )

        array.shift(
             activeArray
        )

        array.shift(
             birthTimes
        )

    0


// ── Bull Zone First Touch ───────────────────────────────────────
f_process_bull_touch(
     array<box> boxes,
     array<float> tops,
     array<float> bottoms,
     array<bool> activeArray,
     array<int> birthTimes
) =>

    bool foundTouch = false

    int count = array.size(
         tops
    )

    if count > 0

        for scan = 0 to count - 1

            int i = count - 1 - scan

            bool active = array.get(
                 activeArray,
                 i
            )

            if active

                float zoneTop = array.get(
                     tops,
                     i
                )

                float zoneBottom = array.get(
                     bottoms,
                     i
                )

                int birthTime = array.get(
                     birthTimes,
                     i
                )

                bool zoneConfirmed = not na(birthTime) and time >= birthTime

                bool touched = (
                     zoneConfirmed and
                     low <= zoneTop and
                     high >= zoneBottom
                )

                if touched

                    foundTouch := true

                    box currentBox = array.get(
                         boxes,
                         i
                    )

                    f_stop_zone(
                         currentBox
                    )

                    array.set(
                         activeArray,
                         i,
                         false
                    )

    foundTouch


// ── Bear Zone First Touch ───────────────────────────────────────
f_process_bear_touch(
     array<box> boxes,
     array<float> tops,
     array<float> bottoms,
     array<bool> activeArray,
     array<int> birthTimes
) =>

    bool foundTouch = false

    int count = array.size(
         tops
    )

    if count > 0

        for scan = 0 to count - 1

            int i = count - 1 - scan

            bool active = array.get(
                 activeArray,
                 i
            )

            if active

                float zoneTop = array.get(
                     tops,
                     i
                )

                float zoneBottom = array.get(
                     bottoms,
                     i
                )

                int birthTime = array.get(
                     birthTimes,
                     i
                )

                bool zoneConfirmed = not na(birthTime) and time >= birthTime

                bool touched = (
                     zoneConfirmed and
                     high >= zoneBottom and
                     low <= zoneTop
                )

                if touched

                    foundTouch := true

                    box currentBox = array.get(
                         boxes,
                         i
                    )

                    f_stop_zone(
                         currentBox
                    )

                    array.set(
                         activeArray,
                         i,
                         false
                    )

    foundTouch


// ── Gap Through Bull Zone ───────────────────────────────────────
f_stop_gapped_bull(
     array<box> boxes,
     array<float> bottoms,
     array<bool> activeArray,
     array<int> birthTimes
) =>

    int count = array.size(
         bottoms
    )

    if count > 0

        for i = 0 to count - 1

            bool active = array.get(
                 activeArray,
                 i
            )

            if active

                float zoneBottom = array.get(
                     bottoms,
                     i
                )

                int birthTime = array.get(
                     birthTimes,
                     i
                )

                bool zoneConfirmed = not na(birthTime) and time >= birthTime
                bool gappedBelow = zoneConfirmed and high < zoneBottom

                if gappedBelow

                    box currentBox = array.get(
                         boxes,
                         i
                    )

                    f_stop_zone(
                         currentBox
                    )

                    array.set(
                         activeArray,
                         i,
                         false
                    )

    0


// ── Gap Through Bear Zone ───────────────────────────────────────
f_stop_gapped_bear(
     array<box> boxes,
     array<float> tops,
     array<bool> activeArray,
     array<int> birthTimes
) =>

    int count = array.size(
         tops
    )

    if count > 0

        for i = 0 to count - 1

            bool active = array.get(
                 activeArray,
                 i
            )

            if active

                float zoneTop = array.get(
                     tops,
                     i
                )

                int birthTime = array.get(
                     birthTimes,
                     i
                )

                bool zoneConfirmed = not na(birthTime) and time >= birthTime
                bool gappedAbove = zoneConfirmed and low > zoneTop

                if gappedAbove

                    box currentBox = array.get(
                         boxes,
                         i
                    )

                    f_stop_zone(
                         currentBox
                    )

                    array.set(
                         activeArray,
                         i,
                         false
                    )

    0


// ════════════════════════════════════════════════════════════════
// CONFIRMED M15 TREND FLIP
// ════════════════════════════════════════════════════════════════

if m15BullTrendFlip

    f_stop_all_active(
         bearOBBoxes,
         bearOBActive
    )

    f_stop_all_active(
         bearFVGBoxes,
         bearFVGActive
    )


if m15BearTrendFlip

    f_stop_all_active(
         bullOBBoxes,
         bullOBActive
    )

    f_stop_all_active(
         bullFVGBoxes,
         bullFVGActive
    )


// ════════════════════════════════════════════════════════════════
// CREATE BULL ZONES
// ════════════════════════════════════════════════════════════════

if m15BullConfirmed and newBullZoneEvent

    // Bull FVG
    if useFVG and not na(m15BullFVGTop) and not na(m15BullFVGBottom)

        float zoneTop = math.max(
             m15BullFVGTop,
             m15BullFVGBottom
        )

        float zoneBottom = math.min(
             m15BullFVGTop,
             m15BullFVGBottom
        )

        box newBullFVG = f_create_zone(
             m15BullFVGStartTime,
             zoneTop,
             zoneBottom,
             bullFVGColor,
             color.new(color.aqua, 15),
             "Bull FVG"
        )

        array.push(
             bullFVGBoxes,
             newBullFVG
        )

        array.push(
             bullFVGTops,
             zoneTop
        )

        array.push(
             bullFVGBottoms,
             zoneBottom
        )

        array.push(
             bullFVGActive,
             true
        )

        array.push(
             bullFVGBirthTimes,
             m15BullZoneEventTime
        )

        f_trim_zone_history(
             bullFVGBoxes,
             bullFVGTops,
             bullFVGBottoms,
             bullFVGActive,
             bullFVGBirthTimes
        )


    // Bull OB
    if useOrderBlock and not na(m15BullOBTop) and not na(m15BullOBBottom)

        float zoneTop = math.max(
             m15BullOBTop,
             m15BullOBBottom
        )

        float zoneBottom = math.min(
             m15BullOBTop,
             m15BullOBBottom
        )

        box newBullOB = f_create_zone(
             m15BullOBTime,
             zoneTop,
             zoneBottom,
             bullOBColor,
             color.new(color.green, 15),
             "Bull OB"
        )

        array.push(
             bullOBBoxes,
             newBullOB
        )

        array.push(
             bullOBTops,
             zoneTop
        )

        array.push(
             bullOBBottoms,
             zoneBottom
        )

        array.push(
             bullOBActive,
             true
        )

        array.push(
             bullOBBirthTimes,
             m15BullZoneEventTime
        )

        f_trim_zone_history(
             bullOBBoxes,
             bullOBTops,
             bullOBBottoms,
             bullOBActive,
             bullOBBirthTimes
        )


// ════════════════════════════════════════════════════════════════
// CREATE BEAR ZONES
// ════════════════════════════════════════════════════════════════

if m15BearConfirmed and newBearZoneEvent

    // Bear FVG
    if useFVG and not na(m15BearFVGTop) and not na(m15BearFVGBottom)

        float zoneTop = math.max(
             m15BearFVGTop,
             m15BearFVGBottom
        )

        float zoneBottom = math.min(
             m15BearFVGTop,
             m15BearFVGBottom
        )

        box newBearFVG = f_create_zone(
             m15BearFVGStartTime,
             zoneTop,
             zoneBottom,
             bearFVGColor,
             color.new(color.orange, 15),
             "Bear FVG"
        )

        array.push(
             bearFVGBoxes,
             newBearFVG
        )

        array.push(
             bearFVGTops,
             zoneTop
        )

        array.push(
             bearFVGBottoms,
             zoneBottom
        )

        array.push(
             bearFVGActive,
             true
        )

        array.push(
             bearFVGBirthTimes,
             m15BearZoneEventTime
        )

        f_trim_zone_history(
             bearFVGBoxes,
             bearFVGTops,
             bearFVGBottoms,
             bearFVGActive,
             bearFVGBirthTimes
        )


    // Bear OB
    if useOrderBlock and not na(m15BearOBTop) and not na(m15BearOBBottom)

        float zoneTop = math.max(
             m15BearOBTop,
             m15BearOBBottom
        )

        float zoneBottom = math.min(
             m15BearOBTop,
             m15BearOBBottom
        )

        box newBearOB = f_create_zone(
             m15BearOBTime,
             zoneTop,
             zoneBottom,
             bearOBColor,
             color.new(color.red, 15),
             "Bear OB"
        )

        array.push(
             bearOBBoxes,
             newBearOB
        )

        array.push(
             bearOBTops,
             zoneTop
        )

        array.push(
             bearOBBottoms,
             zoneBottom
        )

        array.push(
             bearOBActive,
             true
        )

        array.push(
             bearOBBirthTimes,
             m15BearZoneEventTime
        )

        f_trim_zone_history(
             bearOBBoxes,
             bearOBTops,
             bearOBBottoms,
             bearOBActive,
             bearOBBirthTimes
        )


// ════════════════════════════════════════════════════════════════
// FIRST TOUCH PROCESSING
// ════════════════════════════════════════════════════════════════

bool bullOBTouched = f_process_bull_touch(
     bullOBBoxes,
     bullOBTops,
     bullOBBottoms,
     bullOBActive,
     bullOBBirthTimes
)


bool bullFVGTouched = f_process_bull_touch(
     bullFVGBoxes,
     bullFVGTops,
     bullFVGBottoms,
     bullFVGActive,
     bullFVGBirthTimes
)


bool bearOBTouched = f_process_bear_touch(
     bearOBBoxes,
     bearOBTops,
     bearOBBottoms,
     bearOBActive,
     bearOBBirthTimes
)


bool bearFVGTouched = f_process_bear_touch(
     bearFVGBoxes,
     bearFVGTops,
     bearFVGBottoms,
     bearFVGActive,
     bearFVGBirthTimes
)


bool longZoneTouched = bullOBTouched or bullFVGTouched
bool shortZoneTouched = bearOBTouched or bearFVGTouched


// ── Gap Invalidation ────────────────────────────────────────────
f_stop_gapped_bull(
     bullOBBoxes,
     bullOBBottoms,
     bullOBActive,
     bullOBBirthTimes
)

f_stop_gapped_bull(
     bullFVGBoxes,
     bullFVGBottoms,
     bullFVGActive,
     bullFVGBirthTimes
)

f_stop_gapped_bear(
     bearOBBoxes,
     bearOBTops,
     bearOBActive,
     bearOBBirthTimes
)

f_stop_gapped_bear(
     bearFVGBoxes,
     bearFVGTops,
     bearFVGActive,
     bearFVGBirthTimes
)


// ── Trade Direction ─────────────────────────────────────────────
bool canLongDir = tradeDir == "Long" or tradeDir == "Both"
bool canShortDir = tradeDir == "Short" or tradeDir == "Both"


// ════════════════════════════════════════════════════════════════
// M1 SETUP STATE
// ════════════════════════════════════════════════════════════════
//
// Phase:
//
// 0 = no setup
// 1 = zone touched, waiting CHoCH
// 2 = CHoCH confirmed, waiting Pullback + BOS
//
var int longPhase = 0
var int shortPhase = 0

var int longSetupStartBar = na
var int shortSetupStartBar = na


// ── Final entry SL ──────────────────────────────────────────────
//
// Calculated ONLY after BOS confirmation.
//
// Long:
// pullback Pivot Low - buffer.
//
// Short:
// pullback Pivot High + buffer.
//
var float longSetupSL = na
var float shortSetupSL = na


// ── CHoCH References ────────────────────────────────────────────
var float longChochLevel = na
var int longChochLevelBar = na
var int longChochLevelTime = na

var float shortChochLevel = na
var int shortChochLevelBar = na
var int shortChochLevelTime = na


// ── Post-CHoCH Leg ──────────────────────────────────────────────
var int longLegStartBar = na
var int shortLegStartBar = na


// ── Pullback ────────────────────────────────────────────────────
var bool longPullbackSeen = false
var bool shortPullbackSeen = false

var int longPullbackPivotBar = na
var int shortPullbackPivotBar = na

var float longPullbackLow = na
var float shortPullbackHigh = na

var int longPullbackTime = na
var int shortPullbackTime = na


// ── BOS Level ───────────────────────────────────────────────────
var float longBosLevel = na
var int longBosLevelTime = na

var float shortBosLevel = na
var int shortBosLevelTime = na


// ── Events ──────────────────────────────────────────────────────
bool longSetupStarted = false
bool shortSetupStarted = false

bool longExtraZoneTouched = false
bool shortExtraZoneTouched = false

bool longM1CHoCH = false
bool shortM1CHoCH = false

bool longM1BOS = false
bool shortM1BOS = false

bool longBosEntryReady = false
bool shortBosEntryReady = false


// ════════════════════════════════════════════════════════════════
// CONFIRMED M15 FLIP CANCELS OPPOSITE SETUP
// ════════════════════════════════════════════════════════════════

if m15BullTrendFlip

    shortPhase := 0

    shortSetupStartBar := na
    shortSetupSL := na

    shortChochLevel := na
    shortChochLevelBar := na
    shortChochLevelTime := na

    shortLegStartBar := na

    shortPullbackSeen := false
    shortPullbackPivotBar := na
    shortPullbackHigh := na
    shortPullbackTime := na

    shortBosLevel := na
    shortBosLevelTime := na


if m15BearTrendFlip

    longPhase := 0

    longSetupStartBar := na
    longSetupSL := na

    longChochLevel := na
    longChochLevelBar := na
    longChochLevelTime := na

    longLegStartBar := na

    longPullbackSeen := false
    longPullbackPivotBar := na
    longPullbackLow := na
    longPullbackTime := na

    longBosLevel := na
    longBosLevelTime := na


// ════════════════════════════════════════════════════════════════
// ARM LONG SETUP
// ════════════════════════════════════════════════════════════════

bool longTouchEligible = (
     isM1Chart and
     m15BullConfirmed and
     longZoneTouched and
     canLongDir
)


if longTouchEligible

    // First zone touch starts the setup.
    if strategy.position_size <= 0 and longPhase == 0

        longPhase := 1

        longSetupStarted := true

        longSetupStartBar := bar_index
        longSetupSL := na

        // Freeze the latest confirmed M1 Pivot High.
        longChochLevel := lastConfirmedPivotHigh
        longChochLevelBar := lastConfirmedPivotHighBar
        longChochLevelTime := lastConfirmedPivotHighTime

        longLegStartBar := na

        longPullbackSeen := false
        longPullbackPivotBar := na
        longPullbackLow := na
        longPullbackTime := na

        longBosLevel := na
        longBosLevelTime := na


    // Later touched zones do NOT reset the active setup.
    else if strategy.position_size <= 0 and longPhase > 0

        longExtraZoneTouched := true


// ════════════════════════════════════════════════════════════════
// ARM SHORT SETUP
// ════════════════════════════════════════════════════════════════

bool shortTouchEligible = (
     isM1Chart and
     m15BearConfirmed and
     shortZoneTouched and
     canShortDir
)


if shortTouchEligible

    // First zone touch starts the setup.
    if strategy.position_size >= 0 and shortPhase == 0

        shortPhase := 1

        shortSetupStarted := true

        shortSetupStartBar := bar_index
        shortSetupSL := na

        // Freeze the latest confirmed M1 Pivot Low.
        shortChochLevel := lastConfirmedPivotLow
        shortChochLevelBar := lastConfirmedPivotLowBar
        shortChochLevelTime := lastConfirmedPivotLowTime

        shortLegStartBar := na

        shortPullbackSeen := false
        shortPullbackPivotBar := na
        shortPullbackHigh := na
        shortPullbackTime := na

        shortBosLevel := na
        shortBosLevelTime := na


    // Later touched zones do NOT reset the active setup.
    else if strategy.position_size >= 0 and shortPhase > 0

        shortExtraZoneTouched := true


// ════════════════════════════════════════════════════════════════
// FALLBACK CHoCH PIVOT
// ════════════════════════════════════════════════════════════════
//
// Only used when there was no confirmed M1 pivot
// when the original zone was touched.
//

if longPhase == 1 and na(longChochLevel) and not na(m1PivotHigh)

    bool validFallbackLongPivot = (
         not na(longSetupStartBar) and
         m1PivotHighBar >= longSetupStartBar
    )

    if validFallbackLongPivot

        longChochLevel := m1PivotHigh
        longChochLevelBar := m1PivotHighBar
        longChochLevelTime := m1PivotHighTime


if shortPhase == 1 and na(shortChochLevel) and not na(m1PivotLow)

    bool validFallbackShortPivot = (
         not na(shortSetupStartBar) and
         m1PivotLowBar >= shortSetupStartBar
    )

    if validFallbackShortPivot

        shortChochLevel := m1PivotLow
        shortChochLevelBar := m1PivotLowBar
        shortChochLevelTime := m1PivotLowTime


// ════════════════════════════════════════════════════════════════
// LONG CHoCH
// ════════════════════════════════════════════════════════════════

if longPhase == 1 and not na(longChochLevel) and not na(longChochLevelBar)

    float bullChochBreakPrice = useBodyBreak ? close : high
    float previousBullChochBreakPrice = useBodyBreak ? close[1] : high[1]

    bool bullChochBreak = (
         bar_index > longChochLevelBar and
         bullChochBreakPrice > longChochLevel and
         previousBullChochBreakPrice <= longChochLevel
    )


    if bullChochBreak

        longM1CHoCH := true


        if showM1ConfirmStructure and isM1Chart

            f_draw_structure(
                 m1ConfirmLines,
                 m1ConfirmLabels,
                 longChochLevelTime,
                 time_close,
                 longChochLevel,
                 "CHoCH",
                 chochColor
            )


        longPhase := 2

        longLegStartBar := bar_index

        longPullbackSeen := false
        longPullbackPivotBar := na
        longPullbackLow := na
        longPullbackTime := na

        longBosLevel := na
        longBosLevelTime := na


// ════════════════════════════════════════════════════════════════
// SHORT CHoCH
// ════════════════════════════════════════════════════════════════

if shortPhase == 1 and not na(shortChochLevel) and not na(shortChochLevelBar)

    float bearChochBreakPrice = useBodyBreak ? close : low
    float previousBearChochBreakPrice = useBodyBreak ? close[1] : low[1]

    bool bearChochBreak = (
         bar_index > shortChochLevelBar and
         bearChochBreakPrice < shortChochLevel and
         previousBearChochBreakPrice >= shortChochLevel
    )


    if bearChochBreak

        shortM1CHoCH := true


        if showM1ConfirmStructure and isM1Chart

            f_draw_structure(
                 m1ConfirmLines,
                 m1ConfirmLabels,
                 shortChochLevelTime,
                 time_close,
                 shortChochLevel,
                 "CHoCH",
                 chochColor
            )


        shortPhase := 2

        shortLegStartBar := bar_index

        shortPullbackSeen := false
        shortPullbackPivotBar := na
        shortPullbackHigh := na
        shortPullbackTime := na

        shortBosLevel := na
        shortBosLevelTime := na


// ════════════════════════════════════════════════════════════════
// LONG PULLBACK AFTER CHoCH
// ════════════════════════════════════════════════════════════════
//
// After Bull CHoCH:
// wait for a confirmed Pivot Low.
//
// That Pivot Low becomes the future structural SL.
//
// BOS level:
// highest high of CHoCH impulse before pullback.
//
if longPhase == 2 and not longPullbackSeen and not na(m1PivotLow)

    bool validLongPullback = (
         not na(longLegStartBar) and
         m1PivotLowBar > longLegStartBar
    )


    if validLongPullback

        int impulseEndBar = m1PivotLowBar - 1

        [
             foundLongBosLevel,
             foundLongBosTime
        ] = f_highest_between(
             longLegStartBar,
             impulseEndBar
        )


        if not na(foundLongBosLevel)

            longPullbackSeen := true

            longPullbackPivotBar := m1PivotLowBar

            longPullbackLow := m1PivotLow
            longPullbackTime := m1PivotLowTime

            longBosLevel := foundLongBosLevel
            longBosLevelTime := foundLongBosTime


// ════════════════════════════════════════════════════════════════
// SHORT PULLBACK AFTER CHoCH
// ════════════════════════════════════════════════════════════════
//
// After Bear CHoCH:
// wait for a confirmed Pivot High.
//
// That Pivot High becomes the future structural SL.
//
// BOS level:
// lowest low of CHoCH impulse before pullback.
//
if shortPhase == 2 and not shortPullbackSeen and not na(m1PivotHigh)

    bool validShortPullback = (
         not na(shortLegStartBar) and
         m1PivotHighBar > shortLegStartBar
    )


    if validShortPullback

        int impulseEndBar = m1PivotHighBar - 1

        [
             foundShortBosLevel,
             foundShortBosTime
        ] = f_lowest_between(
             shortLegStartBar,
             impulseEndBar
        )


        if not na(foundShortBosLevel)

            shortPullbackSeen := true

            shortPullbackPivotBar := m1PivotHighBar

            shortPullbackHigh := m1PivotHigh
            shortPullbackTime := m1PivotHighTime

            shortBosLevel := foundShortBosLevel
            shortBosLevelTime := foundShortBosTime


// ════════════════════════════════════════════════════════════════
// LONG BOS + STRUCTURAL SL
// ════════════════════════════════════════════════════════════════

if longPhase == 2 and longPullbackSeen and not na(longBosLevel)

    float bullBosBreakPrice = useBodyBreak ? close : high

    bool bullBosBreak = (
         bar_index > longPullbackPivotBar and
         bullBosBreakPrice > longBosLevel
    )


    if bullBosBreak

        longM1BOS := true


        if showM1ConfirmStructure and isM1Chart

            f_draw_structure(
                 m1ConfirmLines,
                 m1ConfirmLabels,
                 longBosLevelTime,
                 time_close,
                 longBosLevel,
                 "BOS",
                 bosColor
            )


        // STRICT ENTRY:
        // BOS confirmation candle must CLOSE above EMA9.
        //
        // SL:
        // below the confirmed pullback Pivot Low.
        if close > ema9 and not na(longPullbackLow)

            longSetupSL :=
                 longPullbackLow -
                 slBuffer

            longBosEntryReady :=
                 true


        // If EMA fails:
        // preserve the setup lineage,
        // but require another Pullback → BOS cycle.
        if not longBosEntryReady

            longLegStartBar :=
                 bar_index

            longPullbackSeen :=
                 false

            longPullbackPivotBar :=
                 na

            longPullbackLow :=
                 na

            longPullbackTime :=
                 na

            longBosLevel :=
                 na

            longBosLevelTime :=
                 na


// ════════════════════════════════════════════════════════════════
// SHORT BOS + STRUCTURAL SL
// ════════════════════════════════════════════════════════════════

if shortPhase == 2 and shortPullbackSeen and not na(shortBosLevel)

    float bearBosBreakPrice = useBodyBreak ? close : low

    bool bearBosBreak = (
         bar_index > shortPullbackPivotBar and
         bearBosBreakPrice < shortBosLevel
    )


    if bearBosBreak

        shortM1BOS := true


        if showM1ConfirmStructure and isM1Chart

            f_draw_structure(
                 m1ConfirmLines,
                 m1ConfirmLabels,
                 shortBosLevelTime,
                 time_close,
                 shortBosLevel,
                 "BOS",
                 bosColor
            )


        // STRICT ENTRY:
        // BOS confirmation candle must CLOSE below EMA9.
        //
        // SL:
        // above the confirmed pullback Pivot High.
        if close < ema9 and not na(shortPullbackHigh)

            shortSetupSL :=
                 shortPullbackHigh +
                 slBuffer

            shortBosEntryReady :=
                 true


        // If EMA fails:
        // preserve setup lineage,
        // but require another Pullback → BOS cycle.
        if not shortBosEntryReady

            shortLegStartBar :=
                 bar_index

            shortPullbackSeen :=
                 false

            shortPullbackPivotBar :=
                 na

            shortPullbackHigh :=
                 na

            shortPullbackTime :=
                 na

            shortBosLevel :=
                 na

            shortBosLevelTime :=
                 na


// ════════════════════════════════════════════════════════════════
// SETUP EXPIRATION
// ════════════════════════════════════════════════════════════════

bool longSetupExpired = false
bool shortSetupExpired = false


if longPhase > 0 and not na(longSetupStartBar)

    longSetupExpired := (
         bar_index - longSetupStartBar >
         confirmWindowBars
    )


if shortPhase > 0 and not na(shortSetupStartBar)

    shortSetupExpired := (
         bar_index - shortSetupStartBar >
         confirmWindowBars
    )


// ════════════════════════════════════════════════════════════════
// CANCEL LONG SETUP
// ════════════════════════════════════════════════════════════════

if longPhase > 0

    bool cancelLong = (
         longSetupExpired or
         not m15BullConfirmed
    )


    if cancelLong

        longPhase := 0

        longSetupStartBar := na
        longSetupSL := na

        longChochLevel := na
        longChochLevelBar := na
        longChochLevelTime := na

        longLegStartBar := na

        longPullbackSeen := false
        longPullbackPivotBar := na

        longPullbackLow := na
        longPullbackTime := na

        longBosLevel := na
        longBosLevelTime := na


// ════════════════════════════════════════════════════════════════
// CANCEL SHORT SETUP
// ════════════════════════════════════════════════════════════════

if shortPhase > 0

    bool cancelShort = (
         shortSetupExpired or
         not m15BearConfirmed
    )


    if cancelShort

        shortPhase := 0

        shortSetupStartBar := na
        shortSetupSL := na

        shortChochLevel := na
        shortChochLevelBar := na
        shortChochLevelTime := na

        shortLegStartBar := na

        shortPullbackSeen := false
        shortPullbackPivotBar := na

        shortPullbackHigh := na
        shortPullbackTime := na

        shortBosLevel := na
        shortBosLevelTime := na


// ── Raw Entry Signals ───────────────────────────────────────────
bool rawLong = (
     isM1Chart and
     m15BullConfirmed and
     longBosEntryReady and
     not na(longSetupSL) and
     canLongDir and
     strategy.position_size <= 0
)


bool rawShort = (
     isM1Chart and
     m15BearConfirmed and
     shortBosEntryReady and
     not na(shortSetupSL) and
     canShortDir and
     strategy.position_size >= 0
)


// ── Opposite M1 Structure Exit ──────────────────────────────────
bool exitLongByStructure = (
     isM1Chart and
     strategy.position_size > 0 and
     (
         globalM1BearCHoCH or
         globalM1BearBOS
     )
)


bool exitShortByStructure = (
     isM1Chart and
     strategy.position_size < 0 and
     (
         globalM1BullCHoCH or
         globalM1BullBOS
     )
)


// ════════════════════════════════════════════════════════════════
// SECTION 5: TIME FILTER
// ════════════════════════════════════════════════════════════════

sessionStart = startHour * 60 + startMinute
sessionEnd = endHour * 60 + endMinute

currentMinutes = hour(time) * 60 + minute(time)

bool inSession = true


if useTimeFilter

    inSession := (
         currentMinutes >= sessionStart and
         currentMinutes < sessionEnd
    )


bool sessionJustEnded = (
     useTimeFilter and
     not inSession and
     inSession[1]
)


// ════════════════════════════════════════════════════════════════
// SECTION 6: ENTRY / EXIT / REVERSE
// ════════════════════════════════════════════════════════════════

bool longEntrySignal = (
     rawLong and
     inSession
)


bool shortEntrySignal = (
     rawShort and
     inSession
)


var float activeLongSL = na
var float activeShortSL = na


// ── Session End ─────────────────────────────────────────────────
if sessionJustEnded

    if strategy.position_size != 0

        strategy.close_all(
             comment="Session End",
             alert_message="SESSION_END"
        )


    longPhase := 0
    shortPhase := 0

    longSetupStartBar := na
    shortSetupStartBar := na

    longSetupSL := na
    shortSetupSL := na

    longChochLevel := na
    shortChochLevel := na

    longChochLevelBar := na
    shortChochLevelBar := na

    longChochLevelTime := na
    shortChochLevelTime := na

    longLegStartBar := na
    shortLegStartBar := na

    longPullbackSeen := false
    shortPullbackSeen := false

    longPullbackPivotBar := na
    shortPullbackPivotBar := na

    longPullbackLow := na
    shortPullbackHigh := na

    longPullbackTime := na
    shortPullbackTime := na

    longBosLevel := na
    shortBosLevel := na

    longBosLevelTime := na
    shortBosLevelTime := na


// ── Structure Exit ──────────────────────────────────────────────
if exitLongByStructure

    strategy.close(
         "Long",
         comment="Exit Long",
         alert_message="EXIT_LONG_M1_STRUCTURE",
         disable_alert=disableStructureExitAlerts
    )


if exitShortByStructure

    strategy.close(
         "Short",
         comment="Exit Short",
         alert_message="EXIT_SHORT_M1_STRUCTURE",
         disable_alert=disableStructureExitAlerts
    )


// ── Long Entry ──────────────────────────────────────────────────
if longEntrySignal

    activeLongSL :=
         longSetupSL

    activeShortSL :=
         na


    strategy.entry(
         "Long",
         strategy.long,
         alert_message="ENTRY_LONG"
    )


    strategy.exit(
         "Exit Long",
         "Long",
         stop=activeLongSL,
         disable_alert=disableSLAlerts
    )


    longPhase := 0

    longSetupStartBar := na
    longSetupSL := na

    longChochLevel := na
    longChochLevelBar := na
    longChochLevelTime := na

    longLegStartBar := na

    longPullbackSeen := false
    longPullbackPivotBar := na

    longPullbackLow := na
    longPullbackTime := na

    longBosLevel := na
    longBosLevelTime := na


// ── Short Entry ─────────────────────────────────────────────────
if shortEntrySignal

    activeShortSL :=
         shortSetupSL

    activeLongSL :=
         na


    strategy.entry(
         "Short",
         strategy.short,
         alert_message="ENTRY_SHORT"
    )


    strategy.exit(
         "Exit Short",
         "Short",
         stop=activeShortSL,
         disable_alert=disableSLAlerts
    )


    shortPhase := 0

    shortSetupStartBar := na
    shortSetupSL := na

    shortChochLevel := na
    shortChochLevelBar := na
    shortChochLevelTime := na

    shortLegStartBar := na

    shortPullbackSeen := false
    shortPullbackPivotBar := na

    shortPullbackHigh := na
    shortPullbackTime := na

    shortBosLevel := na
    shortBosLevelTime := na


// ── Maintain Long SL ────────────────────────────────────────────
if strategy.position_size > 0 and not na(activeLongSL)

    strategy.exit(
         "Exit Long",
         "Long",
         stop=activeLongSL,
         disable_alert=disableSLAlerts
    )


// ── Maintain Short SL ───────────────────────────────────────────
if strategy.position_size < 0 and not na(activeShortSL)

    strategy.exit(
         "Exit Short",
         "Short",
         stop=activeShortSL,
         disable_alert=disableSLAlerts
    )


// ── Reset Stored SL ─────────────────────────────────────────────
if strategy.position_size == 0 and strategy.position_size[1] != 0

    activeLongSL := na
    activeShortSL := na


// ════════════════════════════════════════════════════════════════
// SECTION 7: PLOTTING
// ════════════════════════════════════════════════════════════════

// ── EMA 9 ───────────────────────────────────────────────────────
plot(
     showEMA and isM1Chart ? ema9 : na,
     title="EMA 9",
     color=color.new(color.yellow, 0),
     linewidth=1
)


// ── Entry Markers ───────────────────────────────────────────────
plotshape(
     showSignalShapes and isM1Chart and longEntrySignal,
     title="Long Entry",
     location=location.belowbar,
     color=color.new(color.lime, 0),
     style=shape.triangleup,
     size=size.normal,
     text="LONG",
     textcolor=color.white
)


plotshape(
     showSignalShapes and isM1Chart and shortEntrySignal,
     title="Short Entry",
     location=location.abovebar,
     color=color.new(color.red, 0),
     style=shape.triangledown,
     size=size.normal,
     text="SHORT",
     textcolor=color.white
)


// ── Exit Markers ────────────────────────────────────────────────
plotshape(
     showSignalShapes and isM1Chart and exitLongByStructure,
     title="Long Structure Exit",
     location=location.abovebar,
     color=color.new(color.orange, 0),
     style=shape.xcross,
     size=size.small,
     text="EXIT"
)


plotshape(
     showSignalShapes and isM1Chart and exitShortByStructure,
     title="Short Structure Exit",
     location=location.belowbar,
     color=color.new(color.orange, 0),
     style=shape.xcross,
     size=size.small,
     text="EXIT"
)


// ── Setup Start Markers ─────────────────────────────────────────
plotshape(
     showSetupShapes and isM1Chart and longSetupStarted,
     title="Long Setup Start",
     location=location.belowbar,
     color=color.new(color.green, 10),
     style=shape.circle,
     size=size.tiny,
     text="Z"
)


plotshape(
     showSetupShapes and isM1Chart and shortSetupStarted,
     title="Short Setup Start",
     location=location.abovebar,
     color=color.new(color.red, 10),
     style=shape.circle,
     size=size.tiny,
     text="Z"
)


// ── Additional Zone Touch During Existing Setup ─────────────────
plotshape(
     showSetupShapes and isM1Chart and longExtraZoneTouched,
     title="Extra Bull Zone Touch",
     location=location.belowbar,
     color=color.new(color.aqua, 0),
     style=shape.diamond,
     size=size.tiny,
     text="+Z"
)


plotshape(
     showSetupShapes and isM1Chart and shortExtraZoneTouched,
     title="Extra Bear Zone Touch",
     location=location.abovebar,
     color=color.new(color.orange, 0),
     style=shape.diamond,
     size=size.tiny,
     text="+Z"
)


// ── Pullback Structural SL Reference ────────────────────────────
plot(
     showPendingSL and isM1Chart and longPhase == 2 and not na(longPullbackLow)
     ? longPullbackLow - slBuffer
     : na,
     title="Pending Long Structural SL",
     color=color.new(color.green, 25),
     style=plot.style_linebr,
     linewidth=1
)


plot(
     showPendingSL and isM1Chart and shortPhase == 2 and not na(shortPullbackHigh)
     ? shortPullbackHigh + slBuffer
     : na,
     title="Pending Short Structural SL",
     color=color.new(color.red, 25),
     style=plot.style_linebr,
     linewidth=1
)


// ── Active Position SL ──────────────────────────────────────────
plot(
     isM1Chart and strategy.position_size > 0
     ? activeLongSL
     : na,
     title="Active Long Stop Loss",
     color=color.new(color.red, 0),
     style=plot.style_linebr,
     linewidth=2
)


plot(
     isM1Chart and strategy.position_size < 0
     ? activeShortSL
     : na,
     title="Active Short Stop Loss",
     color=color.new(color.red, 0),
     style=plot.style_linebr,
     linewidth=2
)


// ── Outside Trading Session ─────────────────────────────────────
bgcolor(
     isM1Chart and useTimeFilter and not inSession
     ? color.new(color.gray, 92)
     : na,
     title="Outside Trading Session"
)