// ╔══════════════════════════════════════════════════════════════╗
// ║        CNPS Universal Bot Template — Pine Script v6          ║
// ║  Instrument: VN30! | M5 SMC OB+FVG / M1 Strict Confirm      ║
// ║  Version: CNPS SMC M5-M1 STRICT v3.1 — Clean Chart          ║
// ║  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 12_SMC",
     shorttitle="CNPS 12_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
// ════════════════════════════════════════════════════════════════

// ── M5 Structure ────────────────────────────────────────────────
contextTf = input.timeframe(
     "5",
     title="M5 Context Timeframe",
     group="M5 Structure"
)

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

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


// ── M1 Confirmation ─────────────────────────────────────────────
m1PivotLength = input.int(
     2,
     title="M1 Micro Pivot Length",
     minval=1,
     maxval=10,
     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 M5 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"
)

showM5Structure = input.bool(
     true,
     title="Show M5 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"
)

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)
contextTfSeconds = timeframe.in_seconds(contextTf)

isM1Chart = currentTfSeconds == 60
isM5Chart = currentTfSeconds == contextTfSeconds

useBodyBreak = structureBreakMode == "Candle Close"


// ────────────────────────────────────────────────────────────────
// STRUCTURE HELPERS
// ────────────────────────────────────────────────────────────────

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


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
    ]


// ────────────────────────────────────────────────────────────────
// ORDER BLOCK FINDERS
// ────────────────────────────────────────────────────────────────

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
    ]


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
    ]


// ────────────────────────────────────────────────────────────────
// M5 FVG + ASSOCIATED OB
// ────────────────────────────────────────────────────────────────

f_m5_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
    ]


// ────────────────────────────────────────────────────────────────
// M1 RANGE HELPERS
// ────────────────────────────────────────────────────────────────

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
    ]


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
    ]


// ════════════════════════════════════════════════════════════════
// M5 STRUCTURE DATA
// ════════════════════════════════════════════════════════════════

[
     m5RawDirection,
     m5BullBOS,
     m5BearBOS,
     m5BullCHoCH,
     m5BearCHoCH,
     m5StructureHigh,
     m5StructureLow,
     m5BullEventStartTime,
     m5BullEventEndTime,
     m5BullEventLevel,
     m5BearEventStartTime,
     m5BearEventEndTime,
     m5BearEventLevel
] = request.security(
     syminfo.tickerid,
     contextTf,
     f_structure(
         m5StructureLookback,
         useBodyBreak
     ),
     barmerge.gaps_off,
     barmerge.lookahead_off
)


// ── Unique M5 Events ────────────────────────────────────────────

bool m5BullEventOnce = (
     not na(m5BullEventEndTime) and
     (
         na(m5BullEventEndTime[1]) or
         m5BullEventEndTime != m5BullEventEndTime[1]
     )
)

bool m5BearEventOnce = (
     not na(m5BearEventEndTime) and
     (
         na(m5BearEventEndTime[1]) or
         m5BearEventEndTime != m5BearEventEndTime[1]
     )
)

bool m5BullCHoCHOnce = (
     m5BullEventOnce and
     m5BullCHoCH
)

bool m5BearCHoCHOnce = (
     m5BearEventOnce and
     m5BearCHoCH
)

bool m5BullBOSOnce = (
     m5BullEventOnce and
     m5BullBOS
)

bool m5BearBOSOnce = (
     m5BearEventOnce and
     m5BearBOS
)


// ════════════════════════════════════════════════════════════════
// STRICT M5 CONFIRMED TREND
// ════════════════════════════════════════════════════════════════

var int confirmedM5Trend = 0
var int pendingM5Trend = 0

int confirmedTrendBeforeUpdate = confirmedM5Trend


if m5BullCHoCHOnce

    if confirmedM5Trend == -1
        pendingM5Trend := 1

    else if confirmedM5Trend == 0
        pendingM5Trend := 1

    else if confirmedM5Trend == 1
        pendingM5Trend := 0


if m5BearCHoCHOnce

    if confirmedM5Trend == 1
        pendingM5Trend := -1

    else if confirmedM5Trend == 0
        pendingM5Trend := -1

    else if confirmedM5Trend == -1
        pendingM5Trend := 0


if m5BullBOSOnce

    if pendingM5Trend == 1
        confirmedM5Trend := 1
        pendingM5Trend := 0

    else if confirmedM5Trend == 1
        pendingM5Trend := 0

    else if (
         confirmedM5Trend == 0 and
         pendingM5Trend == 0
    )
        confirmedM5Trend := 1


if m5BearBOSOnce

    if pendingM5Trend == -1
        confirmedM5Trend := -1
        pendingM5Trend := 0

    else if confirmedM5Trend == -1
        pendingM5Trend := 0

    else if (
         confirmedM5Trend == 0 and
         pendingM5Trend == 0
    )
        confirmedM5Trend := -1


bool m5BullConfirmed = confirmedM5Trend == 1
bool m5BearConfirmed = confirmedM5Trend == -1

bool m5BullTrendFlip = (
     confirmedM5Trend == 1 and
     confirmedTrendBeforeUpdate != 1
)

bool m5BearTrendFlip = (
     confirmedM5Trend == -1 and
     confirmedTrendBeforeUpdate != -1
)


// ════════════════════════════════════════════════════════════════
// M5 ZONE DATA
// ════════════════════════════════════════════════════════════════

[
     m5BullZoneEventTime,
     m5BullFVGStartTime,
     m5BullFVGTop,
     m5BullFVGBottom,
     m5BullOBTime,
     m5BullOBTop,
     m5BullOBBottom,
     m5BearZoneEventTime,
     m5BearFVGStartTime,
     m5BearFVGTop,
     m5BearFVGBottom,
     m5BearOBTime,
     m5BearOBTop,
     m5BearOBBottom
] = request.security(
     syminfo.tickerid,
     contextTf,
     f_m5_zone_event(
         obSearchLookback
     ),
     barmerge.gaps_off,
     barmerge.lookahead_off
)


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

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


// ════════════════════════════════════════════════════════════════
// M1 PIVOTS + EMA
// ════════════════════════════════════════════════════════════════

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
)


ema9 = ta.ema(
     close,
     emaLen
)


// ── Global M1 Structure: Exit Only ──────────────────────────────

[
     globalM1Direction,
     globalM1BullBOS,
     globalM1BearBOS,
     globalM1BullCHoCH,
     globalM1BearCHoCH,
     globalM1StructureHigh,
     globalM1StructureLow,
     globalM1BullStart,
     globalM1BullEnd,
     globalM1BullLevel,
     globalM1BearStart,
     globalM1BearEnd,
     globalM1BearLevel
] = f_structure(
     10,
     useBodyBreak
)


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

// M5:
//
// CHoCH → pending reversal.
// CHoCH + BOS → confirmed new trend.
//
// Bull trend:
// create Bull OB + Bull FVG.
//
// Bear trend:
// create Bear OB + Bear FVG.
//
// ZONES:
//
// Zone remains valid until:
// - opposite M5 confirmed trend,
// - or price fully breaks through the wrong side.
//
// Bull zone:
// price must move fully ABOVE zone first.
// Then price returns DOWN into same zone.
// → fresh Long confirmation attempt.
//
// Bear zone:
// price must move fully BELOW zone first.
// Then price returns UP into same zone.
// → fresh Short confirmation attempt.
//
// TOUCH DOES NOT DELETE ZONE.
//
// Every fresh return touch:
// → RESET local M1 setup.
//
// LONG:
// Return to Bull zone
// → Bull CHoCH
// → Pullback Pivot Low
// → First Bull BOS
// → BOS candle close > EMA9
// → Long
// → SL below Pullback Pivot Low.
//
// SHORT:
// Return to Bear zone
// → Bear CHoCH
// → Pullback Pivot High
// → First Bear BOS
// → BOS candle close < EMA9
// → Short
// → SL above Pullback Pivot High.
//
// If first BOS fails EMA:
// confirmation attempt dies.
// Price must leave and return to a valid zone again.


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


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

var array<line> m5StructureLines = array.new_line()
var array<label> m5StructureLabels = 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


// ── M5 Structure: M5 Chart Only ─────────────────────────────────

if (
     isM5Chart and
     showM5Structure and
     m5BullEventOnce
)

    string bullStructureText = (
         m5BullCHoCH
         ? "CHoCH"
         : "BOS"
    )

    color bullStructureColor = (
         m5BullCHoCH
         ? chochColor
         : bosColor
    )

    f_draw_structure(
         m5StructureLines,
         m5StructureLabels,
         m5BullEventStartTime,
         m5BullEventEndTime,
         m5BullEventLevel,
         bullStructureText,
         bullStructureColor
    )


if (
     isM5Chart and
     showM5Structure and
     m5BearEventOnce
)

    string bearStructureText = (
         m5BearCHoCH
         ? "CHoCH"
         : "BOS"
    )

    color bearStructureColor = (
         m5BearCHoCH
         ? chochColor
         : bosColor
    )

    f_draw_structure(
         m5StructureLines,
         m5StructureLabels,
         m5BearEventStartTime,
         m5BearEventEndTime,
         m5BearEventLevel,
         bearStructureText,
         bearStructureColor
    )


// ════════════════════════════════════════════════════════════════
// PERSISTENT 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<bool> bullOBArmed = 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<bool> bullFVGArmed = 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<bool> bearOBArmed = 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<bool> bearFVGArmed = 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 Drawing ───────────────────────────────────────────

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,
     array<bool> armedArray
) =>

    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
                )

                array.set(
                     armedArray,
                     i,
                     false
                )

    0


// ── Trim Zone History ───────────────────────────────────────────

f_trim_zone_history(
     array<box> boxes,
     array<float> tops,
     array<float> bottoms,
     array<bool> activeArray,
     array<bool> armedArray,
     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(
             armedArray
        )

        array.shift(
             birthTimes
        )

    0


// ════════════════════════════════════════════════════════════════
// BULL RETURN-TO-ZONE ENGINE
// ════════════════════════════════════════════════════════════════

f_process_bull_return_touch(
     array<float> tops,
     array<float> bottoms,
     array<bool> activeArray,
     array<bool> armedArray,
     array<int> birthTimes
) =>

    bool foundReturnTouch = 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 armed = array.get(
                     armedArray,
                     i
                )

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


                if zoneKnown

                    if not armed

                        bool fullyAboveZone = (
                             low > zoneTop
                        )

                        if fullyAboveZone
                            array.set(
                                 armedArray,
                                 i,
                                 true
                            )


                    else

                        bool returnedIntoZone = (
                             low <= zoneTop and
                             high >= zoneBottom
                        )

                        if returnedIntoZone

                            foundReturnTouch := true

                            array.set(
                                 armedArray,
                                 i,
                                 false
                            )

    foundReturnTouch


// ════════════════════════════════════════════════════════════════
// BEAR RETURN-TO-ZONE ENGINE
// ════════════════════════════════════════════════════════════════

f_process_bear_return_touch(
     array<float> tops,
     array<float> bottoms,
     array<bool> activeArray,
     array<bool> armedArray,
     array<int> birthTimes
) =>

    bool foundReturnTouch = 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 armed = array.get(
                     armedArray,
                     i
                )

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


                if zoneKnown

                    if not armed

                        bool fullyBelowZone = (
                             high < zoneBottom
                        )

                        if fullyBelowZone
                            array.set(
                                 armedArray,
                                 i,
                                 true
                            )


                    else

                        bool returnedIntoZone = (
                             high >= zoneBottom and
                             low <= zoneTop
                        )

                        if returnedIntoZone

                            foundReturnTouch := true

                            array.set(
                                 armedArray,
                                 i,
                                 false
                            )

    foundReturnTouch


// ════════════════════════════════════════════════════════════════
// ZONE INVALIDATION
// ════════════════════════════════════════════════════════════════

f_invalidate_bull_zones(
     array<box> boxes,
     array<float> bottoms,
     array<bool> activeArray,
     array<bool> armedArray,
     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 zoneKnown = (
                     not na(birthTime) and
                     time >= birthTime
                )

                bool fullyBelowZone = (
                     zoneKnown and
                     high < zoneBottom
                )

                if fullyBelowZone

                    box currentBox = array.get(
                         boxes,
                         i
                    )

                    f_stop_zone(
                         currentBox
                    )

                    array.set(
                         activeArray,
                         i,
                         false
                    )

                    array.set(
                         armedArray,
                         i,
                         false
                    )

    0


f_invalidate_bear_zones(
     array<box> boxes,
     array<float> tops,
     array<bool> activeArray,
     array<bool> armedArray,
     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 zoneKnown = (
                     not na(birthTime) and
                     time >= birthTime
                )

                bool fullyAboveZone = (
                     zoneKnown and
                     low > zoneTop
                )

                if fullyAboveZone

                    box currentBox = array.get(
                         boxes,
                         i
                    )

                    f_stop_zone(
                         currentBox
                    )

                    array.set(
                         activeArray,
                         i,
                         false
                    )

                    array.set(
                         armedArray,
                         i,
                         false
                    )

    0


// ════════════════════════════════════════════════════════════════
// M5 TREND FLIP
// ════════════════════════════════════════════════════════════════

if m5BullTrendFlip

    f_stop_all_active(
         bearOBBoxes,
         bearOBActive,
         bearOBArmed
    )

    f_stop_all_active(
         bearFVGBoxes,
         bearFVGActive,
         bearFVGArmed
    )


if m5BearTrendFlip

    f_stop_all_active(
         bullOBBoxes,
         bullOBActive,
         bullOBArmed
    )

    f_stop_all_active(
         bullFVGBoxes,
         bullFVGActive,
         bullFVGArmed
    )


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

if (
     m5BullConfirmed and
     newBullZoneEvent
)

    // ── Bull FVG ────────────────────────────────────────────────
    if (
         useFVG and
         not na(m5BullFVGTop) and
         not na(m5BullFVGBottom)
    )

        float bullFVGZoneTop = math.max(
             m5BullFVGTop,
             m5BullFVGBottom
        )

        float bullFVGZoneBottom = math.min(
             m5BullFVGTop,
             m5BullFVGBottom
        )

        box newBullFVG = f_create_zone(
             m5BullFVGStartTime,
             bullFVGZoneTop,
             bullFVGZoneBottom,
             bullFVGColor,
             color.new(color.aqua, 15),
             "Bull FVG"
        )

        array.push(
             bullFVGBoxes,
             newBullFVG
        )

        array.push(
             bullFVGTops,
             bullFVGZoneTop
        )

        array.push(
             bullFVGBottoms,
             bullFVGZoneBottom
        )

        array.push(
             bullFVGActive,
             true
        )

        array.push(
             bullFVGArmed,
             false
        )

        array.push(
             bullFVGBirthTimes,
             m5BullZoneEventTime
        )

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


    // ── Bull OB ─────────────────────────────────────────────────
    if (
         useOrderBlock and
         not na(m5BullOBTop) and
         not na(m5BullOBBottom)
    )

        float bullOBZoneTop = math.max(
             m5BullOBTop,
             m5BullOBBottom
        )

        float bullOBZoneBottom = math.min(
             m5BullOBTop,
             m5BullOBBottom
        )

        box newBullOB = f_create_zone(
             m5BullOBTime,
             bullOBZoneTop,
             bullOBZoneBottom,
             bullOBColor,
             color.new(color.green, 15),
             "Bull OB"
        )

        array.push(
             bullOBBoxes,
             newBullOB
        )

        array.push(
             bullOBTops,
             bullOBZoneTop
        )

        array.push(
             bullOBBottoms,
             bullOBZoneBottom
        )

        array.push(
             bullOBActive,
             true
        )

        array.push(
             bullOBArmed,
             false
        )

        array.push(
             bullOBBirthTimes,
             m5BullZoneEventTime
        )

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


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

if (
     m5BearConfirmed and
     newBearZoneEvent
)

    // ── Bear FVG ────────────────────────────────────────────────
    if (
         useFVG and
         not na(m5BearFVGTop) and
         not na(m5BearFVGBottom)
    )

        float bearFVGZoneTop = math.max(
             m5BearFVGTop,
             m5BearFVGBottom
        )

        float bearFVGZoneBottom = math.min(
             m5BearFVGTop,
             m5BearFVGBottom
        )

        box newBearFVG = f_create_zone(
             m5BearFVGStartTime,
             bearFVGZoneTop,
             bearFVGZoneBottom,
             bearFVGColor,
             color.new(color.orange, 15),
             "Bear FVG"
        )

        array.push(
             bearFVGBoxes,
             newBearFVG
        )

        array.push(
             bearFVGTops,
             bearFVGZoneTop
        )

        array.push(
             bearFVGBottoms,
             bearFVGZoneBottom
        )

        array.push(
             bearFVGActive,
             true
        )

        array.push(
             bearFVGArmed,
             false
        )

        array.push(
             bearFVGBirthTimes,
             m5BearZoneEventTime
        )

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


    // ── Bear OB ─────────────────────────────────────────────────
    if (
         useOrderBlock and
         not na(m5BearOBTop) and
         not na(m5BearOBBottom)
    )

        float bearOBZoneTop = math.max(
             m5BearOBTop,
             m5BearOBBottom
        )

        float bearOBZoneBottom = math.min(
             m5BearOBTop,
             m5BearOBBottom
        )

        box newBearOB = f_create_zone(
             m5BearOBTime,
             bearOBZoneTop,
             bearOBZoneBottom,
             bearOBColor,
             color.new(color.red, 15),
             "Bear OB"
        )

        array.push(
             bearOBBoxes,
             newBearOB
        )

        array.push(
             bearOBTops,
             bearOBZoneTop
        )

        array.push(
             bearOBBottoms,
             bearOBZoneBottom
        )

        array.push(
             bearOBActive,
             true
        )

        array.push(
             bearOBArmed,
             false
        )

        array.push(
             bearOBBirthTimes,
             m5BearZoneEventTime
        )

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


// ════════════════════════════════════════════════════════════════
// ZONE INVALIDATION
// ════════════════════════════════════════════════════════════════

f_invalidate_bull_zones(
     bullOBBoxes,
     bullOBBottoms,
     bullOBActive,
     bullOBArmed,
     bullOBBirthTimes
)

f_invalidate_bull_zones(
     bullFVGBoxes,
     bullFVGBottoms,
     bullFVGActive,
     bullFVGArmed,
     bullFVGBirthTimes
)

f_invalidate_bear_zones(
     bearOBBoxes,
     bearOBTops,
     bearOBActive,
     bearOBArmed,
     bearOBBirthTimes
)

f_invalidate_bear_zones(
     bearFVGBoxes,
     bearFVGTops,
     bearFVGActive,
     bearFVGArmed,
     bearFVGBirthTimes
)


// ════════════════════════════════════════════════════════════════
// RETURN-TO-ZONE PROCESSING
// ════════════════════════════════════════════════════════════════

bool bullOBReturnTouch = f_process_bull_return_touch(
     bullOBTops,
     bullOBBottoms,
     bullOBActive,
     bullOBArmed,
     bullOBBirthTimes
)

bool bullFVGReturnTouch = f_process_bull_return_touch(
     bullFVGTops,
     bullFVGBottoms,
     bullFVGActive,
     bullFVGArmed,
     bullFVGBirthTimes
)

bool bearOBReturnTouch = f_process_bear_return_touch(
     bearOBTops,
     bearOBBottoms,
     bearOBActive,
     bearOBArmed,
     bearOBBirthTimes
)

bool bearFVGReturnTouch = f_process_bear_return_touch(
     bearFVGTops,
     bearFVGBottoms,
     bearFVGActive,
     bearFVGArmed,
     bearFVGBirthTimes
)


bool longReturnTouch = (
     bullOBReturnTouch or
     bullFVGReturnTouch
)

bool shortReturnTouch = (
     bearOBReturnTouch or
     bearFVGReturnTouch
)


// ── Trade Direction ─────────────────────────────────────────────

bool canLongDir = (
     tradeDir == "Long" or
     tradeDir == "Both"
)

bool canShortDir = (
     tradeDir == "Short" or
     tradeDir == "Both"
)


// ════════════════════════════════════════════════════════════════
// M1 LOCAL CONFIRMATION STATE
// ════════════════════════════════════════════════════════════════
//
// 0 = no attempt
// 1 = fresh zone return → waiting CHoCH
// 2 = CHoCH → waiting Pullback + BOS
// ════════════════════════════════════════════════════════════════

var int longPhase = 0
var int shortPhase = 0

var int longLastTouchBar = na
var int shortLastTouchBar = na

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 Levels ──────────────────────────────────────────────────

var float longBosLevel = na
var int longBosLevelTime = na

var float shortBosLevel = na
var int shortBosLevelTime = na


// ── Current Bar Events ──────────────────────────────────────────

bool longM1CHoCH = false
bool shortM1CHoCH = false

bool longBosEntryReady = false
bool shortBosEntryReady = false


// ════════════════════════════════════════════════════════════════
// M5 TREND FLIP CANCELS OPPOSITE M1 SETUP
// ════════════════════════════════════════════════════════════════

if m5BullTrendFlip

    shortPhase := 0

    shortLastTouchBar := na
    shortSetupSL := na

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

    shortLegStartBar := na

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

    shortBosLevel := na
    shortBosLevelTime := na


if m5BearTrendFlip

    longPhase := 0

    longLastTouchBar := na
    longSetupSL := na

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

    longLegStartBar := na

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

    longBosLevel := na
    longBosLevelTime := na


// ════════════════════════════════════════════════════════════════
// FRESH LONG ATTEMPT ON RETURN TO BULL ZONE
// ════════════════════════════════════════════════════════════════

bool longTouchEligible = (
     isM1Chart and
     m5BullConfirmed and
     longReturnTouch and
     canLongDir and
     strategy.position_size <= 0
)


if longTouchEligible

    longPhase := 1

    longLastTouchBar := bar_index

    longSetupSL := na

    longChochLevel := lastConfirmedPivotHigh
    longChochLevelBar := lastConfirmedPivotHighBar
    longChochLevelTime := lastConfirmedPivotHighTime

    longLegStartBar := na

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

    longBosLevel := na
    longBosLevelTime := na


// ════════════════════════════════════════════════════════════════
// FRESH SHORT ATTEMPT ON RETURN TO BEAR ZONE
// ════════════════════════════════════════════════════════════════

bool shortTouchEligible = (
     isM1Chart and
     m5BearConfirmed and
     shortReturnTouch and
     canShortDir and
     strategy.position_size >= 0
)


if shortTouchEligible

    shortPhase := 1

    shortLastTouchBar := bar_index

    shortSetupSL := na

    shortChochLevel := lastConfirmedPivotLow
    shortChochLevelBar := lastConfirmedPivotLowBar
    shortChochLevelTime := lastConfirmedPivotLowTime

    shortLegStartBar := na

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

    shortBosLevel := na
    shortBosLevelTime := na


// ════════════════════════════════════════════════════════════════
// FALLBACK CHoCH PIVOT
// ════════════════════════════════════════════════════════════════

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

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

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


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

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

    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 >= longLastTouchBar and
         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 >= shortLastTouchBar and
         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
// ════════════════════════════════════════════════════════════════

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

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 + EMA CONFIRM
// ════════════════════════════════════════════════════════════════

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

    float bullBosBreakPrice = (
         useBodyBreak
         ? close
         : high
    )

    float previousBullBosBreakPrice = (
         useBodyBreak
         ? close[1]
         : high[1]
    )

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


    if bullBosBreak

        if (
             showM1ConfirmStructure and
             isM1Chart
        )

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


        if (
             close > ema9 and
             not na(longPullbackLow)
        )

            longSetupSL := (
                 longPullbackLow -
                 slBuffer
            )

            longBosEntryReady := true


        else

            // First BOS failed EMA.
            // Attempt dies.
            // Must leave + return to zone again.
            longPhase := 0

            longLastTouchBar := na
            longSetupSL := na

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

            longLegStartBar := na

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

            longBosLevel := na
            longBosLevelTime := na


// ════════════════════════════════════════════════════════════════
// SHORT BOS + EMA CONFIRM
// ════════════════════════════════════════════════════════════════

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

    float bearBosBreakPrice = (
         useBodyBreak
         ? close
         : low
    )

    float previousBearBosBreakPrice = (
         useBodyBreak
         ? close[1]
         : low[1]
    )

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


    if bearBosBreak

        if (
             showM1ConfirmStructure and
             isM1Chart
        )

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


        if (
             close < ema9 and
             not na(shortPullbackHigh)
        )

            shortSetupSL := (
                 shortPullbackHigh +
                 slBuffer
            )

            shortBosEntryReady := true


        else

            shortPhase := 0

            shortLastTouchBar := na
            shortSetupSL := na

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

            shortLegStartBar := na

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

            shortBosLevel := na
            shortBosLevelTime := na


// ── Trend Mismatch Cancel ───────────────────────────────────────

if (
     longPhase > 0 and
     not m5BullConfirmed
)

    longPhase := 0

    longLastTouchBar := na
    longSetupSL := na

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

    longLegStartBar := na

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

    longBosLevel := na
    longBosLevelTime := na


if (
     shortPhase > 0 and
     not m5BearConfirmed
)

    shortPhase := 0

    shortLastTouchBar := 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
     m5BullConfirmed and
     longBosEntryReady and
     not na(longSetupSL) and
     canLongDir and
     strategy.position_size <= 0
)

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


// ── Opposite Global 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

    longLastTouchBar := na
    shortLastTouchBar := 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


// ── M1 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

    longLastTouchBar := 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

    shortLastTouchBar := 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
)


// ── Long Entry ──────────────────────────────────────────────────

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
)


// ── Short Entry ─────────────────────────────────────────────────

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
)


// ── Long Structure Exit ─────────────────────────────────────────

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"
)


// ── Short Structure 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"
)


// ── Pending Long Structural SL ──────────────────────────────────

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
)


// ── Pending Short Structural SL ─────────────────────────────────

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 Long 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
)


// ── Active Short SL ─────────────────────────────────────────────

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 Session ─────────────────────────────────────────────

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