//@version=6
strategy(
     title="CNPS29 VP SMC STRUCTIQ",
     shorttitle="CNPS29 SMC",
     overlay=true,
     initial_capital=100000000,
     default_qty_type=strategy.fixed,
     default_qty_value=3,
     pyramiding=0,
     margin_long=0,
     margin_short=0,
     slippage=3,
     commission_type=strategy.commission.cash_per_contract,
     commission_value=10000,
     fill_orders_on_standard_ohlc=true,
     process_orders_on_close=true,
     calc_on_order_fills=true,
     max_labels_count=300,
     max_lines_count=300,
     max_bars_back=5000
)


// ============================================================================
// CNPS29 VP SMC STRUCTIQ
//
// SOURCE-DERIVED ENTRY ENGINE:
//
// CONFIRMED SWINGS
//       ↓
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ 1. LIQUIDITY SWEEP                                                    │
// │    Sweep swing -> reclaim -> optional later-candle confirmation        │
// │                                                                         │
// │ 2. CHoCH                                                               │
// │    Break opposite to current structure trend                           │
// │                                                                         │
// │ 3. BOS                                                                 │
// │    Break with current / neutral structure trend                         │
// │    + optional strong displacement candle                               │
// └─────────────────────────────────────────────────────────────────────────┘
//
// PRIORITY:
// SWEEP > CHoCH > BOS
//
// CNPS EXECUTION:
// - Current chart timeframe
// - 100M VND
// - 3 contracts
// - Commission 10,000 VND / contract
// - Slippage 3 ticks
// - 09:00 -> 14:30 entries
// - Force close 14:45
//
// DEFAULT EXIT:
// - Fixed SL = 5 points
// - Fixed TP = 15 points
//
// OPTIONAL SOURCE EXIT:
// - Structure stop
// - RR target
// ============================================================================


// ============================================================================
// SECTION 1: ENTRY CONDITIONS
// ============================================================================

swingLength = input.int(
     3,
     title="Swing Confirmation Bars",
     minval=1,
     maxval=30,
     group="Entry Conditions"
)

useSweep = input.bool(
     true,
     title="Liquidity Sweep",
     group="Entry Conditions"
)

useChoch = input.bool(
     true,
     title="CHoCH",
     group="Entry Conditions"
)

useBos = input.bool(
     true,
     title="BOS",
     group="Entry Conditions"
)


// ============================================================================
// SECTION 2: ENTRY QUALITY
// ============================================================================

confirmSweep = input.bool(
     true,
     title="Confirm Sweep Before Entry",
     group="Entry Quality"
)

sweepWindow = input.int(
     3,
     title="Sweep Confirmation Window",
     minval=1,
     maxval=10,
     group="Entry Quality"
)

strongBos = input.bool(
     true,
     title="Strong BOS Candle Filter",
     group="Entry Quality"
)

minimumBosBodyPercent = input.float(
     60.0,
     title="BOS Minimum Body %",
     minval=10.0,
     maxval=100.0,
     step=5.0,
     group="Entry Quality"
)

bosCloseEdgePercent = input.float(
     25.0,
     title="BOS Close Within Edge %",
     minval=5.0,
     maxval=50.0,
     step=5.0,
     group="Entry Quality"
)


// ============================================================================
// SECTION 3: DIRECTION
// ============================================================================

tradeDirection = input.string(
     "Both",
     title="Trade Direction",
     options=["Long", "Short", "Both"],
     group="Trade Control"
)

bool allowLong = tradeDirection == "Long" or tradeDirection == "Both"
bool allowShort = tradeDirection == "Short" or tradeDirection == "Both"

maximumTradesPerDay = input.int(
     6,
     title="Maximum Trades Per Day",
     minval=1,
     maxval=30,
     group="Trade Control"
)

cooldownBars = input.int(
     0,
     title="Cooldown Bars After Exit",
     minval=0,
     maxval=100,
     group="Trade Control"
)


// ============================================================================
// SECTION 4: EXIT MODE
// ============================================================================

exitMode = input.string(
     "Fixed Points",
     title="Exit Mode",
     options=[
         "Fixed Points",
         "Source Structure RR"
     ],
     group="Risk Management"
)


// ============================================================================
// SECTION 5: FIXED POINT EXIT
// ============================================================================

fixedStopLossPoints = input.float(
     5.0,
     title="Fixed Stop Loss Points",
     minval=0.5,
     step=0.5,
     group="Risk Management"
)

fixedTakeProfitPoints = input.float(
     15.0,
     title="Fixed Take Profit Points",
     minval=0.5,
     step=0.5,
     group="Risk Management"
)


// ============================================================================
// SECTION 6: SOURCE STRUCTURE EXIT
// ============================================================================

structureRewardRisk = input.float(
     1.0,
     title="Source Reward / Risk",
     minval=0.5,
     maxval=10.0,
     step=0.25,
     group="Structure Risk"
)

structureBufferPoints = input.float(
     0.0,
     title="Structure Risk Buffer Points",
     minval=0.0,
     step=0.5,
     group="Structure Risk"
)

maximumStructureRiskPoints = input.float(
     0.0,
     title="Maximum Structure Risk (0 = Off)",
     minval=0.0,
     step=0.5,
     group="Structure Risk"
)


// ============================================================================
// SECTION 7: OPTIONAL BREAK EVEN
// ============================================================================

useBreakEven = input.bool(
     false,
     title="Use Break Even?",
     group="Break Even"
)

breakEvenTriggerR = input.float(
     1.0,
     title="Break Even Trigger (R)",
     minval=0.25,
     maxval=5.0,
     step=0.25,
     group="Break Even"
)

breakEvenOffsetPoints = input.float(
     0.5,
     title="Break Even Offset Points",
     minval=0.0,
     step=0.5,
     group="Break Even"
)


// ============================================================================
// SECTION 8: SESSION
// ============================================================================

strategyTimezone = input.string(
     "Asia/Ho_Chi_Minh",
     title="Strategy Timezone",
     group="Session"
)

morningOnly = input.bool(
     false,
     title="Morning Entries Only?",
     group="Session"
)

sessionStartHour = input.int(
     9,
     title="Entry Start Hour",
     minval=0,
     maxval=23,
     group="Session"
)

sessionStartMinute = input.int(
     0,
     title="Entry Start Minute",
     minval=0,
     maxval=59,
     group="Session"
)

morningEndHour = input.int(
     11,
     title="Morning End Hour",
     minval=0,
     maxval=23,
     group="Session"
)

morningEndMinute = input.int(
     30,
     title="Morning End Minute",
     minval=0,
     maxval=59,
     group="Session"
)

lastEntryHour = input.int(
     14,
     title="Last Entry Hour",
     minval=0,
     maxval=23,
     group="Session"
)

lastEntryMinute = input.int(
     30,
     title="Last Entry Minute",
     minval=0,
     maxval=59,
     group="Session"
)

sessionEndHour = input.int(
     14,
     title="Force Close Hour",
     minval=0,
     maxval=23,
     group="Session"
)

sessionEndMinute = input.int(
     45,
     title="Force Close Minute",
     minval=0,
     maxval=59,
     group="Session"
)


// ============================================================================
// SECTION 9: ALERTS
// ============================================================================

disableSLTPAlerts = input.bool(
     false,
     title="Disable SL / TP Alerts?",
     group="Alerts"
)


// ============================================================================
// SECTION 10: DISPLAY
// ============================================================================

showSwings = input.bool(
     false,
     title="Show Current Swing Levels?",
     group="Display"
)

showStructureSignals = input.bool(
     false,
     title="Show SWEEP / CHoCH / BOS Markers?",
     group="Display"
)

showEntrySignals = input.bool(
     false,
     title="Show Entry Markers?",
     group="Display"
)

showActiveSLTP = input.bool(
     true,
     title="Show Active SL / TP?",
     group="Display"
)

showDiagnostics = input.bool(
     false,
     title="Show Diagnostics?",
     group="Display"
)


// ============================================================================
// SECTION 11: TIME ENGINE
// ============================================================================

int currentMinute = hour(time, strategyTimezone) * 60 + minute(time, strategyTimezone)

int currentCloseMinute = hour(time_close, strategyTimezone) * 60 + minute(time_close, strategyTimezone)

int sessionStartTotal = sessionStartHour * 60 + sessionStartMinute

int morningEndTotal = morningEndHour * 60 + morningEndMinute

int lastEntryTotal = lastEntryHour * 60 + lastEntryMinute

int sessionEndTotal = sessionEndHour * 60 + sessionEndMinute


bool normalEntrySession = currentMinute >= sessionStartTotal and currentMinute <= lastEntryTotal

bool morningEntrySession = currentMinute >= sessionStartTotal and currentMinute <= morningEndTotal

bool entrySession = morningOnly ? morningEntrySession : normalEntrySession


// ============================================================================
// SECTION 12: DAILY COUNTER
// ============================================================================

int dayKey = year(time, strategyTimezone) * 10000 + month(time, strategyTimezone) * 100 + dayofmonth(time, strategyTimezone)

bool newTradingDay = bar_index == 0 or dayKey != nz(dayKey[1], dayKey)

var int tradesToday = 0

if newTradingDay
    tradesToday := 0


// ============================================================================
// SECTION 13: CONFIRMED PIVOTS
// ============================================================================
//
// IMPORTANT:
//
// These are calculated here but only assigned to swingHigh / swingLow
// AFTER all signal processing.
//
// This preserves the source behavior:
// newly confirmed pivots become usable on the NEXT candle.
//
// ============================================================================

float newPivotHigh = ta.pivothigh(
     high,
     swingLength,
     swingLength
)

float newPivotLow = ta.pivotlow(
     low,
     swingLength,
     swingLength
)


// ============================================================================
// SECTION 14: STRUCTURE STATE
// ============================================================================

var float swingHigh = na

var float swingLow = na

var bool highBroken = false

var bool lowBroken = false

var bool highSwept = false

var bool lowSwept = false


// Trend:
//  1 = Bullish
// -1 = Bearish
//  0 = Undefined

var int structureTrend = 0


// ============================================================================
// SECTION 15: PENDING SWEEP STATE
// ============================================================================
//
// Side:
//  1 = Pending Bullish Sweep
// -1 = Pending Bearish Sweep
//  0 = None
//
// ============================================================================

var int pendingSweepSide = 0

var int pendingSweepBar = na

var float pendingSweepTrigger = na

var float pendingSweepExtreme = na

var float pendingSweepSwing = na


if newTradingDay

    pendingSweepSide := 0

    pendingSweepBar := na

    pendingSweepTrigger := na

    pendingSweepExtreme := na

    pendingSweepSwing := na


// ============================================================================
// SECTION 16: CANDLE QUALITY
// ============================================================================

float candleRange = high - low

float candleBody = math.abs(close - open)

float bodyPercent = 0.0

if candleRange > 0
    bodyPercent := 100.0 * candleBody / candleRange


bool strongBull = (
     candleRange > 0 and
     close > open and
     bodyPercent >= minimumBosBodyPercent and
     close >= high - candleRange * bosCloseEdgePercent / 100.0
)


bool strongBear = (
     candleRange > 0 and
     close < open and
     bodyPercent >= minimumBosBodyPercent and
     close <= low + candleRange * bosCloseEdgePercent / 100.0
)


// ============================================================================
// SECTION 17: POSITION STATE
// ============================================================================

bool flat = strategy.position_size == 0

bool inLong = strategy.position_size > 0

bool inShort = strategy.position_size < 0

bool newLongPosition = inLong and strategy.position_size[1] <= 0

bool newShortPosition = inShort and strategy.position_size[1] >= 0

bool positionJustClosed = flat and strategy.position_size[1] != 0


// ============================================================================
// SECTION 18: COOLDOWN
// ============================================================================

var int lastExitBar = na

if positionJustClosed
    lastExitBar := bar_index


bool cooldownOK = na(lastExitBar) or bar_index - lastExitBar > cooldownBars

bool tradeLimitOK = tradesToday < maximumTradesPerDay


// ============================================================================
// SECTION 19: BREAK OF STRUCTURE
// ============================================================================
//
// Uses previously confirmed swing state.
//
// ============================================================================

bool breakUp = (
     not na(swingHigh) and
     not highBroken and
     close > swingHigh and
     close[1] <= swingHigh and
     barstate.isconfirmed
)


bool breakDown = (
     not na(swingLow) and
     not lowBroken and
     close < swingLow and
     close[1] >= swingLow and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 20: CHOCH / BOS ATTRIBUTION
// ============================================================================

bool bullChoch = breakUp and structureTrend == -1

bool bearChoch = breakDown and structureTrend == 1

bool bullBos = breakUp and structureTrend != -1

bool bearBos = breakDown and structureTrend != 1


// ============================================================================
// SECTION 21: LIQUIDITY SWEEP
// ============================================================================

bool bullSweep = (
     not na(swingLow) and
     not lowBroken and
     not lowSwept and
     low < swingLow and
     close > swingLow and
     close > open and
     barstate.isconfirmed
)


bool bearSweep = (
     not na(swingHigh) and
     not highBroken and
     not highSwept and
     high > swingHigh and
     close < swingHigh and
     close < open and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 22: UPDATE STRUCTURE FLAGS
// ============================================================================
//
// Source updates these BEFORE deciding final setup attribution.
//
// ============================================================================

if breakUp

    highBroken := true

    structureTrend := 1


if breakDown

    lowBroken := true

    structureTrend := -1


if bullSweep

    lowSwept := true


if bearSweep

    highSwept := true


// ============================================================================
// SECTION 23: ENTRY AVAILABILITY
// ============================================================================
//
// Prevent same-bar re-entry after a position just exited.
//
// ============================================================================

bool canEnter = (
     flat and
     not positionJustClosed and
     entrySession and
     currentCloseMinute < sessionEndTotal and
     cooldownOK and
     tradeLimitOK and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 24: CANCEL PENDING SWEEP WHEN NOT ELIGIBLE
// ============================================================================

if (
     not flat or
     positionJustClosed or
     not entrySession or
     currentCloseMinute >= sessionEndTotal
)

    pendingSweepSide := 0


// ============================================================================
// SECTION 25: PROCESS PENDING SWEEP
// ============================================================================

bool confirmedBullSweep = false

bool confirmedBearSweep = false

float confirmedSweepRisk = na


if (
     confirmSweep and
     useSweep and
     canEnter and
     pendingSweepSide != 0
)

    int elapsedSweepBars = bar_index - pendingSweepBar


    bool pendingInvalidated = false


    if pendingSweepSide == 1
        pendingInvalidated := low < pendingSweepExtreme

    else
        pendingInvalidated := high > pendingSweepExtreme


    if elapsedSweepBars > sweepWindow or pendingInvalidated

        pendingSweepSide := 0


    else if elapsedSweepBars >= 1

        bool pendingTriggered = false


        if pendingSweepSide == 1
            pendingTriggered := close > pendingSweepTrigger

        else
            pendingTriggered := close < pendingSweepTrigger


        if pendingTriggered

            confirmedBullSweep := pendingSweepSide == 1

            confirmedBearSweep := pendingSweepSide == -1


            if pendingSweepSide == 1
                confirmedSweepRisk := math.min(pendingSweepSwing, pendingSweepExtreme)

            else
                confirmedSweepRisk := math.max(pendingSweepSwing, pendingSweepExtreme)


            pendingSweepSide := 0


// ============================================================================
// SECTION 26: REGISTER FRESH SWEEP
// ============================================================================
//
// Do NOT enter on this candle when Confirm Sweep is ON.
//
// ============================================================================

if (
     confirmSweep and
     useSweep and
     canEnter and
     not confirmedBullSweep and
     not confirmedBearSweep
)

    if bullSweep and not bearSweep

        pendingSweepSide := 1

        pendingSweepBar := bar_index

        pendingSweepTrigger := high

        pendingSweepExtreme := low

        pendingSweepSwing := swingLow


    else if bearSweep and not bullSweep

        pendingSweepSide := -1

        pendingSweepBar := bar_index

        pendingSweepTrigger := low

        pendingSweepExtreme := high

        pendingSweepSwing := swingHigh


// ============================================================================
// SECTION 27: FINAL SETUP TYPES
// ============================================================================

bool sweepLong = (
     useSweep and
     (
         confirmSweep
         ? confirmedBullSweep
         : bullSweep
     )
)


bool sweepShort = (
     useSweep and
     (
         confirmSweep
         ? confirmedBearSweep
         : bearSweep
     )
)


bool chochLong = useChoch and bullChoch

bool chochShort = useChoch and bearChoch


bool bosLong = (
     useBos and
     bullBos and
     (
         not strongBos or
         strongBull
     )
)


bool bosShort = (
     useBos and
     bearBos and
     (
         not strongBos or
         strongBear
     )
)


// ============================================================================
// SECTION 28: COMBINED SETUPS
// ============================================================================

bool longSetup = sweepLong or chochLong or bosLong

bool shortSetup = sweepShort or chochShort or bosShort


// ============================================================================
// SECTION 29: SELECT SETUP WITH SOURCE PRIORITY
// ============================================================================
//
// Setup ID:
// 0 = SWEEP
// 1 = CHoCH
// 2 = BOS
//
// Priority:
// SWEEP > CHoCH > BOS
//
// ============================================================================

int selectedSide = 0

int selectedSetupId = -1

string selectedSetupName = ""

float selectedStructureStop = na


if (
     canEnter and
     longSetup and
     not shortSetup and
     not na(swingLow) and
     allowLong
)

    selectedSide := 1


    if sweepLong

        selectedSetupId := 0

        selectedSetupName := confirmSweep ? "SWEEP CONFIRMED" : "SWEEP"


    else if chochLong

        selectedSetupId := 1

        selectedSetupName := "CHoCH"


    else

        selectedSetupId := 2

        selectedSetupName := "BOS"


    float longBaseRisk = swingLow


    if sweepLong and confirmSweep
        longBaseRisk := confirmedSweepRisk


    selectedStructureStop := math.min(longBaseRisk, low) - structureBufferPoints


    if close - selectedStructureStop <= syminfo.mintick

        selectedSide := 0

        selectedSetupId := -1

        selectedSetupName := ""

        selectedStructureStop := na


if (
     canEnter and
     shortSetup and
     not longSetup and
     not na(swingHigh) and
     allowShort
)

    selectedSide := -1


    if sweepShort

        selectedSetupId := 0

        selectedSetupName := confirmSweep ? "SWEEP CONFIRMED" : "SWEEP"


    else if chochShort

        selectedSetupId := 1

        selectedSetupName := "CHoCH"


    else

        selectedSetupId := 2

        selectedSetupName := "BOS"


    float shortBaseRisk = swingHigh


    if sweepShort and confirmSweep
        shortBaseRisk := confirmedSweepRisk


    selectedStructureStop := math.max(shortBaseRisk, high) + structureBufferPoints


    if selectedStructureStop - close <= syminfo.mintick

        selectedSide := 0

        selectedSetupId := -1

        selectedSetupName := ""

        selectedStructureStop := na


// ============================================================================
// SECTION 30: OPTIONAL STRUCTURE-RISK FILTER
// ============================================================================

bool structureRiskValid = true


if (
     exitMode == "Source Structure RR" and
     selectedSide != 0 and
     not na(selectedStructureStop) and
     maximumStructureRiskPoints > 0
)

    float estimatedStructureRisk = 0.0


    if selectedSide == 1
        estimatedStructureRisk := close - selectedStructureStop

    else
        estimatedStructureRisk := selectedStructureStop - close


    if estimatedStructureRisk > maximumStructureRiskPoints
        structureRiskValid := false


// ============================================================================
// SECTION 31: FINAL ENTRY SIGNALS
// ============================================================================

bool finalLongSignal = (
     selectedSide == 1 and
     structureRiskValid
)


bool finalShortSignal = (
     selectedSide == -1 and
     structureRiskValid
)


// ============================================================================
// SECTION 32: PENDING ENTRY CONTEXT
// ============================================================================

var float pendingStructureStop = na

var int pendingSetupId = -1

var string pendingSetupName = ""


// ============================================================================
// SECTION 33: LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingStructureStop := selectedStructureStop

    pendingSetupId := selectedSetupId

    pendingSetupName := selectedSetupName


    string longEntryAlert = "ENTRY_LONG_CNPS29_" + selectedSetupName


    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment=selectedSetupName,
         alert_message=longEntryAlert
    )


    tradesToday += 1

    pendingSweepSide := 0


// ============================================================================
// SECTION 34: SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingStructureStop := selectedStructureStop

    pendingSetupId := selectedSetupId

    pendingSetupName := selectedSetupName


    string shortEntryAlert = "ENTRY_SHORT_CNPS29_" + selectedSetupName


    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment=selectedSetupName,
         alert_message=shortEntryAlert
    )


    tradesToday += 1

    pendingSweepSide := 0


// ============================================================================
// SECTION 35: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeInitialRisk = na

var float activeLongSL = na

var float activeLongTP = na

var float activeShortSL = na

var float activeShortTP = na

var int activeEntryBar = na

var int activeSetupId = -1

var string activeSetupName = ""

var bool breakEvenActivated = false


// ============================================================================
// SECTION 36: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeEntryBar := bar_index

    activeSetupId := pendingSetupId

    activeSetupName := pendingSetupName

    breakEvenActivated := false


    if exitMode == "Source Structure RR"

        float longStructureRisk = activeEntryPrice - pendingStructureStop


        if na(longStructureRisk) or longStructureRisk <= syminfo.mintick
            longStructureRisk := fixedStopLossPoints


        activeInitialRisk := longStructureRisk

        activeLongSL := activeEntryPrice - activeInitialRisk

        activeLongTP := activeEntryPrice + activeInitialRisk * structureRewardRisk


    else

        activeInitialRisk := fixedStopLossPoints

        activeLongSL := activeEntryPrice - fixedStopLossPoints

        activeLongTP := activeEntryPrice + fixedTakeProfitPoints


    activeShortSL := na

    activeShortTP := na


    pendingStructureStop := na

    pendingSetupId := -1

    pendingSetupName := ""


// ============================================================================
// SECTION 37: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeEntryBar := bar_index

    activeSetupId := pendingSetupId

    activeSetupName := pendingSetupName

    breakEvenActivated := false


    if exitMode == "Source Structure RR"

        float shortStructureRisk = pendingStructureStop - activeEntryPrice


        if na(shortStructureRisk) or shortStructureRisk <= syminfo.mintick
            shortStructureRisk := fixedStopLossPoints


        activeInitialRisk := shortStructureRisk

        activeShortSL := activeEntryPrice + activeInitialRisk

        activeShortTP := activeEntryPrice - activeInitialRisk * structureRewardRisk


    else

        activeInitialRisk := fixedStopLossPoints

        activeShortSL := activeEntryPrice + fixedStopLossPoints

        activeShortTP := activeEntryPrice - fixedTakeProfitPoints


    activeLongSL := na

    activeLongTP := na


    pendingStructureStop := na

    pendingSetupId := -1

    pendingSetupName := ""


// ============================================================================
// SECTION 38: BREAK EVEN LONG
// ============================================================================

if (
     inLong and
     useBreakEven and
     not breakEvenActivated and
     not na(activeEntryPrice) and
     not na(activeInitialRisk) and
     activeInitialRisk > 0
)

    float longBreakEvenTrigger = activeEntryPrice + activeInitialRisk * breakEvenTriggerR


    if high >= longBreakEvenTrigger

        float newLongStop = activeEntryPrice + breakEvenOffsetPoints


        if na(activeLongSL) or newLongStop > activeLongSL
            activeLongSL := newLongStop


        breakEvenActivated := true


// ============================================================================
// SECTION 39: BREAK EVEN SHORT
// ============================================================================

if (
     inShort and
     useBreakEven and
     not breakEvenActivated and
     not na(activeEntryPrice) and
     not na(activeInitialRisk) and
     activeInitialRisk > 0
)

    float shortBreakEvenTrigger = activeEntryPrice - activeInitialRisk * breakEvenTriggerR


    if low <= shortBreakEvenTrigger

        float newShortStop = activeEntryPrice - breakEvenOffsetPoints


        if na(activeShortSL) or newShortStop < activeShortSL
            activeShortSL := newShortStop


        breakEvenActivated := true


// ============================================================================
// SECTION 40: LONG EXIT
// ============================================================================
//
// Source explicitly performs no exit check on the entry candle.
// We preserve that by requiring bar_index > activeEntryBar.
//
// ============================================================================

if (
     inLong and
     not na(activeEntryBar) and
     bar_index > activeEntryBar and
     not na(activeLongSL) and
     not na(activeLongTP)
)

    string longLossMessage = breakEvenActivated ? "EXIT_LONG_BE_CNPS29" : "EXIT_LONG_SL_CNPS29"


    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=activeLongSL,
         limit=activeLongTP,
         alert_profit="EXIT_LONG_TP_CNPS29",
         alert_loss=longLossMessage,
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 41: SHORT EXIT
// ============================================================================

if (
     inShort and
     not na(activeEntryBar) and
     bar_index > activeEntryBar and
     not na(activeShortSL) and
     not na(activeShortTP)
)

    string shortLossMessage = breakEvenActivated ? "EXIT_SHORT_BE_CNPS29" : "EXIT_SHORT_SL_CNPS29"


    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=activeShortSL,
         limit=activeShortTP,
         alert_profit="EXIT_SHORT_TP_CNPS29",
         alert_loss=shortLossMessage,
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 42: POSITION CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeInitialRisk := na

    activeLongSL := na

    activeLongTP := na

    activeShortSL := na

    activeShortTP := na

    activeEntryBar := na

    activeSetupId := -1

    activeSetupName := ""

    breakEvenActivated := false


// ============================================================================
// SECTION 43: SESSION END
// ============================================================================

bool finalSessionBar = (
     currentMinute < sessionEndTotal and
     currentCloseMinute >= sessionEndTotal
)


if finalSessionBar

    pendingSweepSide := 0


    if strategy.position_size != 0

        strategy.close_all(
             immediately=true,
             comment="Session End",
             alert_message="SESSION_END_CNPS29"
        )


// ============================================================================
// SECTION 44: IMPORTANT - UPDATE NEW PIVOTS LAST
// ============================================================================
//
// Exactly like the indicator:
// new pivots confirmed on this candle are only usable from the next candle.
//
// ============================================================================

if barstate.isconfirmed

    if not na(newPivotHigh)

        swingHigh := newPivotHigh

        highBroken := false

        highSwept := false


    if not na(newPivotLow)

        swingLow := newPivotLow

        lowBroken := false

        lowSwept := false


// ============================================================================
// SECTION 45: SWING DISPLAY
// ============================================================================

plot(
     showSwings ? swingHigh : na,
     title="Swing High",
     color=color.new(color.red, 45),
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showSwings ? swingLow : na,
     title="Swing Low",
     color=color.new(color.green, 45),
     linewidth=1,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 46: ACTIVE SL / TP
// ============================================================================

plot(
     showActiveSLTP and inLong ? activeLongSL : na,
     title="Long SL",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showActiveSLTP and inLong ? activeLongTP : na,
     title="Long TP",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showActiveSLTP and inShort ? activeShortSL : na,
     title="Short SL",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showActiveSLTP and inShort ? activeShortTP : na,
     title="Short TP",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 47: STRUCTURE EVENT MARKERS
// ============================================================================

plotshape(
     showStructureSignals and bullSweep,
     title="Bull Sweep",
     style=shape.circle,
     location=location.belowbar,
     color=color.lime,
     size=size.tiny,
     text="SW"
)


plotshape(
     showStructureSignals and bearSweep,
     title="Bear Sweep",
     style=shape.circle,
     location=location.abovebar,
     color=color.orange,
     size=size.tiny,
     text="SW"
)


plotshape(
     showStructureSignals and bullChoch,
     title="Bull CHoCH",
     style=shape.diamond,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="CH"
)


plotshape(
     showStructureSignals and bearChoch,
     title="Bear CHoCH",
     style=shape.diamond,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="CH"
)


plotshape(
     showStructureSignals and bosLong,
     title="Bull BOS",
     style=shape.square,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="B"
)


plotshape(
     showStructureSignals and bosShort,
     title="Bear BOS",
     style=shape.square,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="B"
)


// ============================================================================
// SECTION 48: ENTRY MARKERS
// ============================================================================

plotshape(
     showEntrySignals and finalLongSignal,
     title="Long Entry",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.small,
     text="L",
     textcolor=color.white
)


plotshape(
     showEntrySignals and finalShortSignal,
     title="Short Entry",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.small,
     text="S",
     textcolor=color.white
)


// ============================================================================
// SECTION 49: DIAGNOSTIC COUNTERS
// ============================================================================

var int bullSweepCount = 0

var int bearSweepCount = 0

var int bullChochCount = 0

var int bearChochCount = 0

var int bullBosCount = 0

var int bearBosCount = 0

var int sweepEntryCount = 0

var int chochEntryCount = 0

var int bosEntryCount = 0


if bullSweep
    bullSweepCount += 1


if bearSweep
    bearSweepCount += 1


if bullChoch
    bullChochCount += 1


if bearChoch
    bearChochCount += 1


if bosLong
    bullBosCount += 1


if bosShort
    bearBosCount += 1


if finalLongSignal or finalShortSignal

    if selectedSetupId == 0
        sweepEntryCount += 1

    else if selectedSetupId == 1
        chochEntryCount += 1

    else if selectedSetupId == 2
        bosEntryCount += 1


// ============================================================================
// SECTION 50: DIAGNOSTICS
// ============================================================================

var table diagnostics = table.new(
     position.top_right,
     2,
     20,
     border_width=1
)


if showDiagnostics and barstate.islast

    string trendText = "NEUTRAL"

    if structureTrend == 1
        trendText := "BULL"

    else if structureTrend == -1
        trendText := "BEAR"


    string pendingText = "NONE"

    if pendingSweepSide == 1
        pendingText := "BULL SWEEP"

    else if pendingSweepSide == -1
        pendingText := "BEAR SWEEP"


    string positionText = "FLAT"

    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


    table.cell(diagnostics, 0, 0, "CNPS29")
    table.cell(diagnostics, 1, 0, timeframe.period)


    table.cell(diagnostics, 0, 1, "Trend")
    table.cell(diagnostics, 1, 1, trendText)


    table.cell(diagnostics, 0, 2, "Swing High")
    table.cell(diagnostics, 1, 2, str.tostring(swingHigh, format.mintick))


    table.cell(diagnostics, 0, 3, "Swing Low")
    table.cell(diagnostics, 1, 3, str.tostring(swingLow, format.mintick))


    table.cell(diagnostics, 0, 4, "Pending Sweep")
    table.cell(diagnostics, 1, 4, pendingText)


    table.cell(diagnostics, 0, 5, "Body %")
    table.cell(diagnostics, 1, 5, str.tostring(bodyPercent, "0.0"))


    table.cell(diagnostics, 0, 6, "Bull Sweep")
    table.cell(diagnostics, 1, 6, str.tostring(bullSweepCount))


    table.cell(diagnostics, 0, 7, "Bear Sweep")
    table.cell(diagnostics, 1, 7, str.tostring(bearSweepCount))


    table.cell(diagnostics, 0, 8, "Bull CHoCH")
    table.cell(diagnostics, 1, 8, str.tostring(bullChochCount))


    table.cell(diagnostics, 0, 9, "Bear CHoCH")
    table.cell(diagnostics, 1, 9, str.tostring(bearChochCount))


    table.cell(diagnostics, 0, 10, "Bull BOS")
    table.cell(diagnostics, 1, 10, str.tostring(bullBosCount))


    table.cell(diagnostics, 0, 11, "Bear BOS")
    table.cell(diagnostics, 1, 11, str.tostring(bearBosCount))


    table.cell(diagnostics, 0, 12, "Sweep Entries")
    table.cell(diagnostics, 1, 12, str.tostring(sweepEntryCount))


    table.cell(diagnostics, 0, 13, "CHoCH Entries")
    table.cell(diagnostics, 1, 13, str.tostring(chochEntryCount))


    table.cell(diagnostics, 0, 14, "BOS Entries")
    table.cell(diagnostics, 1, 14, str.tostring(bosEntryCount))


    table.cell(diagnostics, 0, 15, "Active Setup")
    table.cell(diagnostics, 1, 15, activeSetupName == "" ? "NONE" : activeSetupName)


    table.cell(diagnostics, 0, 16, "Active Risk")
    table.cell(diagnostics, 1, 16, str.tostring(activeInitialRisk, "0.00"))


    table.cell(diagnostics, 0, 17, "Trades Today")
    table.cell(diagnostics, 1, 17, str.tostring(tradesToday) + " / " + str.tostring(maximumTradesPerDay))


    table.cell(diagnostics, 0, 18, "Exit Mode")
    table.cell(diagnostics, 1, 18, exitMode)


    table.cell(diagnostics, 0, 19, "Position")
    table.cell(diagnostics, 1, 19, positionText)