//@version=6
strategy(
     title="CNPS33 OPENING RANGE DOUBLE BREAKOUT",
     shorttitle="CNPS33 DOUBLE BO",
     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_lines_count=100,
     max_labels_count=100,
     max_bars_back=5000
)


// ============================================================================
// CNPS33 OPENING RANGE DOUBLE BREAKOUT
//
// SOURCE CONCEPT:
//
// STEP 1
// Build one fixed opening range.
//
// STEP 2
// First breakout:
// Close > Range High
//      -> store breakout candle HIGH
//
// or
//
// Close < Range Low
//      -> store breakout candle LOW
//
// NO ENTRY YET.
//
// STEP 3
// Second breakout:
//
// LONG:
// Close > stored breakout HIGH
//
// SHORT:
// Close < stored breakout LOW
//
// STEP 4
// Source-style risk:
//
// LONG SL = Range Low
// SHORT SL = Range High
//
// TP = Risk * approximately 2.247R
//
// After a completed / skipped setup:
// Reset and search for another first breakout.
//
// CNPS ADAPTATION:
// - VN opening range default 09:00 -> 09:05
// - Current chart timeframe
// - Works best on M1 / M5
// - 100M VND
// - 3 contracts
// - Commission 10,000 / contract
// - Slippage 3 ticks
// - Max 3 trades/day
// - Last setup 14:30
// - Force close 14:45
// ============================================================================


// ============================================================================
// SECTION 1: OPENING RANGE
// ============================================================================

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

rangeStartHour = input.int(
     9,
     title="Range Start Hour",
     minval=0,
     maxval=23,
     group="Opening Range"
)

rangeStartMinute = input.int(
     0,
     title="Range Start Minute",
     minval=0,
     maxval=59,
     group="Opening Range"
)

rangeEndHour = input.int(
     9,
     title="Range End Hour",
     minval=0,
     maxval=23,
     group="Opening Range"
)

rangeEndMinute = input.int(
     5,
     title="Range End Minute",
     minval=0,
     maxval=59,
     group="Opening Range"
)


// ============================================================================
// SECTION 2: SETUP EXPIRATION
// ============================================================================

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

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

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

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


// ============================================================================
// SECTION 3: ALLOWED DAYS
// ============================================================================

tradeMonday = input.bool(
     true,
     title="Monday",
     group="Allowed Days"
)

tradeTuesday = input.bool(
     true,
     title="Tuesday",
     group="Allowed Days"
)

tradeWednesday = input.bool(
     true,
     title="Wednesday",
     group="Allowed Days"
)

tradeThursday = input.bool(
     true,
     title="Thursday",
     group="Allowed Days"
)

tradeFriday = input.bool(
     true,
     title="Friday",
     group="Allowed Days"
)


// ============================================================================
// SECTION 4: TRADE CONTROL
// ============================================================================

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(
     3,
     title="Maximum Trades Per Day",
     minval=1,
     maxval=10,
     group="Trade Control"
)

skipFirstSetups = input.int(
     0,
     title="Skip First Valid Setups",
     minval=0,
     maxval=2,
     group="Trade Control"
)

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


// ============================================================================
// SECTION 5: EXIT MODE
// ============================================================================

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


// ============================================================================
// SECTION 6: SOURCE RANGE RISK
// ============================================================================

sourceRewardRisk = input.float(
     2.247,
     title="Source Reward / Risk",
     minval=0.5,
     maxval=10.0,
     step=0.001,
     group="Source Range Risk"
)

stopBufferPoints = input.float(
     0.0,
     title="Stop Buffer Points",
     minval=0.0,
     step=0.1,
     group="Source Range Risk"
)

targetFrontRunPoints = input.float(
     0.8,
     title="TP Front-Run Buffer Points",
     minval=0.0,
     step=0.1,
     group="Source Range Risk"
)

minimumRangeRisk = input.float(
     0.0,
     title="Minimum Risk Points (0 = Off)",
     minval=0.0,
     step=0.5,
     group="Source Range Risk"
)

maximumRangeRisk = input.float(
     0.0,
     title="Maximum Risk Points (0 = Off)",
     minval=0.0,
     step=0.5,
     group="Source Range Risk"
)


// ============================================================================
// SECTION 7: SOURCE TP EXTENSION
// ============================================================================
//
// Source option:
//
// Instead of taking profit at TP,
// price reaches the TP activation level
// and a native trailing stop begins.
//
// OFF by default.
//
// ============================================================================

useTargetExtension = input.bool(
     false,
     title="Enable TP Extension Trailing?",
     group="Source Range Risk"
)

extensionTrailPoints = input.float(
     1.0,
     title="Extension Trail Distance Points",
     minval=0.1,
     step=0.1,
     group="Source Range Risk"
)


// ============================================================================
// SECTION 8: FIXED CNPS EXIT
// ============================================================================

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

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


// ============================================================================
// SECTION 9: OPTIONAL BREAK EVEN
// ============================================================================
//
// Not part of the original strategy.
// OFF by default.
//
// ============================================================================

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 10: ALERTS
// ============================================================================

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


// ============================================================================
// SECTION 11: DISPLAY
// ============================================================================

showRange = input.bool(
     true,
     title="Show Opening Range?",
     group="Display"
)

showBreakoutLevel = input.bool(
     true,
     title="Show Armed Breakout Level?",
     group="Display"
)

showSignals = input.bool(
     false,
     title="Show Signal Markers?",
     group="Display"
)

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

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


// ============================================================================
// SECTION 12: TIME ENGINE
// ============================================================================

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

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

int rangeStartTotal = (
     rangeStartHour * 60 +
     rangeStartMinute
)

int rangeEndTotal = (
     rangeEndHour * 60 +
     rangeEndMinute
)

int lastSetupTotal = (
     lastSetupHour * 60 +
     lastSetupMinute
)

int forceCloseTotal = (
     forceCloseHour * 60 +
     forceCloseMinute
)


// ============================================================================
// SECTION 13: DAY FILTER
// ============================================================================

int localDayOfWeek = dayofweek(
     time,
     strategyTimezone
)

bool allowedDay = false


if localDayOfWeek == dayofweek.monday

    allowedDay := tradeMonday


else if localDayOfWeek == dayofweek.tuesday

    allowedDay := tradeTuesday


else if localDayOfWeek == dayofweek.wednesday

    allowedDay := tradeWednesday


else if localDayOfWeek == dayofweek.thursday

    allowedDay := tradeThursday


else if localDayOfWeek == dayofweek.friday

    allowedDay := tradeFriday


// ============================================================================
// SECTION 14: RANGE WINDOW
// ============================================================================

bool inRangeWindow = (
     allowedDay and
     currentMinute >= rangeStartTotal and
     currentMinute < rangeEndTotal
)

bool rangeWindowStart = (
     inRangeWindow and
     not inRangeWindow[1]
)

bool rangeWindowEnd = (
     not inRangeWindow and
     inRangeWindow[1]
)


// ============================================================================
// SECTION 15: DAILY KEY
// ============================================================================

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

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


// ============================================================================
// SECTION 16: RANGE STATE
// ============================================================================

var float rangeHigh = na

var float rangeLow = na

var bool rangeReady = false


// ============================================================================
// SECTION 17: DOUBLE BREAKOUT STATE
// ============================================================================

var bool lookingForFirstBreakout = false

var bool lookingForSecondLong = false

var bool lookingForSecondShort = false

var float firstBreakoutHigh = na

var float firstBreakoutLow = na

var int firstBreakoutBar = na


// ============================================================================
// SECTION 18: DAILY COUNTERS
// ============================================================================

var int tradesToday = 0

var int skippedSetupsToday = 0

var int firstBullBreakCount = 0

var int firstBearBreakCount = 0

var int secondBullBreakCount = 0

var int secondBearBreakCount = 0


// ============================================================================
// SECTION 19: NEW DAY RESET
// ============================================================================

if newTradingDay

    rangeHigh := na

    rangeLow := na

    rangeReady := false

    lookingForFirstBreakout := false

    lookingForSecondLong := false

    lookingForSecondShort := false

    firstBreakoutHigh := na

    firstBreakoutLow := na

    firstBreakoutBar := na

    tradesToday := 0

    skippedSetupsToday := 0


// ============================================================================
// SECTION 20: BUILD OPENING RANGE
// ============================================================================
//
// On M1:
// Accumulates the five 1-minute candles from 09:00 to 09:05.
//
// On M5:
// Uses the single 09:00 -> 09:05 candle.
//
// ============================================================================

if rangeWindowStart

    rangeHigh := high

    rangeLow := low

    rangeReady := false

    lookingForFirstBreakout := false

    lookingForSecondLong := false

    lookingForSecondShort := false

    firstBreakoutHigh := na

    firstBreakoutLow := na

    firstBreakoutBar := na

    tradesToday := 0

    skippedSetupsToday := 0


else if inRangeWindow

    rangeHigh := math.max(
         rangeHigh,
         high
    )

    rangeLow := math.min(
         rangeLow,
         low
    )


// ============================================================================
// SECTION 21: ACTIVATE RANGE
// ============================================================================

if (
     rangeWindowEnd and
     not na(rangeHigh) and
     not na(rangeLow)
)

    rangeReady := true

    lookingForFirstBreakout := true

    lookingForSecondLong := false

    lookingForSecondShort := false

    firstBreakoutHigh := na

    firstBreakoutLow := na

    firstBreakoutBar := na


// ============================================================================
// SECTION 22: 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 23: COOLDOWN
// ============================================================================

var int lastExitBar = na


if positionJustClosed

    lastExitBar := bar_index


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


// ============================================================================
// SECTION 24: SETUP WINDOW
// ============================================================================

bool setupWindowOpen = (
     allowedDay and
     rangeReady and
     currentMinute >= rangeEndTotal and
     currentMinute <= lastSetupTotal
)


// ============================================================================
// SECTION 25: EXPIRE SETUPS
// ============================================================================

bool setupExpirationBar = (
     currentMinute < lastSetupTotal and
     currentCloseMinute >= lastSetupTotal
)


if (
     setupExpirationBar or
     currentMinute > lastSetupTotal
)

    lookingForFirstBreakout := false

    lookingForSecondLong := false

    lookingForSecondShort := false


// ============================================================================
// SECTION 26: FIRST BREAKOUT LONG
// ============================================================================
//
// This does NOT enter a trade.
//
// It only stores the HIGH of the breakout candle.
//
// ============================================================================

bool firstBullBreak = (
     setupWindowOpen and
     lookingForFirstBreakout and
     flat and
     tradesToday < maximumTradesPerDay and
     allowLong and
     close > rangeHigh and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 27: FIRST BREAKOUT SHORT
// ============================================================================

bool firstBearBreak = (
     setupWindowOpen and
     lookingForFirstBreakout and
     flat and
     tradesToday < maximumTradesPerDay and
     allowShort and
     close < rangeLow and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 28: ARM FIRST BREAKOUT
// ============================================================================

if (
     firstBullBreak and
     not firstBearBreak
)

    firstBreakoutHigh := high

    firstBreakoutLow := na

    firstBreakoutBar := bar_index

    lookingForSecondLong := true

    lookingForSecondShort := false

    lookingForFirstBreakout := false

    firstBullBreakCount += 1


else if (
     firstBearBreak and
     not firstBullBreak
)

    firstBreakoutLow := low

    firstBreakoutHigh := na

    firstBreakoutBar := bar_index

    lookingForSecondShort := true

    lookingForSecondLong := false

    lookingForFirstBreakout := false

    firstBearBreakCount += 1


// ============================================================================
// SECTION 29: SECOND BREAKOUT LONG
// ============================================================================

bool secondBullBreak = (
     setupWindowOpen and
     lookingForSecondLong and
     not na(firstBreakoutHigh) and
     bar_index > firstBreakoutBar and
     close > firstBreakoutHigh and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 30: SECOND BREAKOUT SHORT
// ============================================================================

bool secondBearBreak = (
     setupWindowOpen and
     lookingForSecondShort and
     not na(firstBreakoutLow) and
     bar_index > firstBreakoutBar and
     close < firstBreakoutLow and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 31: SECOND BREAKOUT COUNTERS
// ============================================================================

if secondBullBreak
    secondBullBreakCount += 1


if secondBearBreak
    secondBearBreakCount += 1


// ============================================================================
// SECTION 32: SKIP FIRST VALID SETUPS
// ============================================================================

bool skipLongSetup = (
     secondBullBreak and
     skippedSetupsToday < skipFirstSetups
)

bool skipShortSetup = (
     secondBearBreak and
     skippedSetupsToday < skipFirstSetups
)


if skipLongSetup

    skippedSetupsToday += 1

    lookingForFirstBreakout := true

    lookingForSecondLong := false

    firstBreakoutHigh := na

    firstBreakoutBar := na


if skipShortSetup

    skippedSetupsToday += 1

    lookingForFirstBreakout := true

    lookingForSecondShort := false

    firstBreakoutLow := na

    firstBreakoutBar := na


// ============================================================================
// SECTION 33: SOURCE STOP ESTIMATE
// ============================================================================

float prospectiveLongStop = na

float prospectiveShortStop = na


if secondBullBreak

    prospectiveLongStop := (
         rangeLow -
         stopBufferPoints
    )


if secondBearBreak

    prospectiveShortStop := (
         rangeHigh +
         stopBufferPoints
    )


// ============================================================================
// SECTION 34: PROSPECTIVE SOURCE RISK
// ============================================================================

float prospectiveLongRisk = na

float prospectiveShortRisk = na


if not na(prospectiveLongStop)

    prospectiveLongRisk := (
         close -
         prospectiveLongStop
    )


if not na(prospectiveShortStop)

    prospectiveShortRisk := (
         prospectiveShortStop -
         close
    )


// ============================================================================
// SECTION 35: RANGE RISK FILTER
// ============================================================================

bool longRangeRiskOK = true

bool shortRangeRiskOK = true


if exitMode == "Source Range RR"

    if secondBullBreak

        longRangeRiskOK := (
             not na(prospectiveLongRisk) and
             prospectiveLongRisk > syminfo.mintick
        )


        if (
             longRangeRiskOK and
             minimumRangeRisk > 0
        )

            longRangeRiskOK := (
                 prospectiveLongRisk >= minimumRangeRisk
            )


        if (
             longRangeRiskOK and
             maximumRangeRisk > 0
        )

            longRangeRiskOK := (
                 prospectiveLongRisk <= maximumRangeRisk
            )


    if secondBearBreak

        shortRangeRiskOK := (
             not na(prospectiveShortRisk) and
             prospectiveShortRisk > syminfo.mintick
        )


        if (
             shortRangeRiskOK and
             minimumRangeRisk > 0
        )

            shortRangeRiskOK := (
                 prospectiveShortRisk >= minimumRangeRisk
            )


        if (
             shortRangeRiskOK and
             maximumRangeRisk > 0
        )

            shortRangeRiskOK := (
                 prospectiveShortRisk <= maximumRangeRisk
            )


// ============================================================================
// SECTION 36: FINAL ENTRY APPROVAL
// ============================================================================

bool commonEntryApproval = (
     flat and
     setupWindowOpen and
     tradesToday < maximumTradesPerDay and
     cooldownOK and
     barstate.isconfirmed
)


bool finalLongSignal = (
     commonEntryApproval and
     secondBullBreak and
     not skipLongSetup and
     allowLong and
     longRangeRiskOK
)


bool finalShortSignal = (
     commonEntryApproval and
     secondBearBreak and
     not skipShortSetup and
     allowShort and
     shortRangeRiskOK
)


// ============================================================================
// SECTION 37: REJECT INVALID-RISK SETUP
// ============================================================================
//
// A second breakout has occurred.
// If the fixed-quantity range risk is rejected,
// consume that setup and search for a fresh first breakout.
//
// ============================================================================

if (
     secondBullBreak and
     not skipLongSetup and
     not longRangeRiskOK
)

    lookingForFirstBreakout := true

    lookingForSecondLong := false

    firstBreakoutHigh := na

    firstBreakoutBar := na


if (
     secondBearBreak and
     not skipShortSetup and
     not shortRangeRiskOK
)

    lookingForFirstBreakout := true

    lookingForSecondShort := false

    firstBreakoutLow := na

    firstBreakoutBar := na


// ============================================================================
// SECTION 38: PENDING TRADE CONTEXT
// ============================================================================

var float pendingStopPrice = na

var float pendingRiskDistance = na


// ============================================================================
// SECTION 39: LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingStopPrice := prospectiveLongStop

    pendingRiskDistance := prospectiveLongRisk


    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment="2BO L",
         alert_message="ENTRY_LONG_CNPS33"
    )


    tradesToday += 1


    lookingForFirstBreakout := true

    lookingForSecondLong := false

    lookingForSecondShort := false

    firstBreakoutHigh := na

    firstBreakoutLow := na

    firstBreakoutBar := na


// ============================================================================
// SECTION 40: SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingStopPrice := prospectiveShortStop

    pendingRiskDistance := prospectiveShortRisk


    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment="2BO S",
         alert_message="ENTRY_SHORT_CNPS33"
    )


    tradesToday += 1


    lookingForFirstBreakout := true

    lookingForSecondLong := false

    lookingForSecondShort := false

    firstBreakoutHigh := na

    firstBreakoutLow := na

    firstBreakoutBar := na


// ============================================================================
// SECTION 41: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeInitialRisk = na

var float activeStop = na

var float activeTarget = na

var bool breakEvenActivated = false

var int activeEntryBar = na


// ============================================================================
// SECTION 42: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeEntryBar := bar_index

    breakEvenActivated := false


    if exitMode == "Source Range RR"

        activeStop := pendingStopPrice

        activeInitialRisk := (
             activeEntryPrice -
             activeStop
        )


        if (
             na(activeInitialRisk) or
             activeInitialRisk <= syminfo.mintick
        )

            activeInitialRisk := fixedStopLossPoints

            activeStop := (
                 activeEntryPrice -
                 activeInitialRisk
            )


        activeTarget := (
             activeEntryPrice +
             activeInitialRisk *
             sourceRewardRisk -
             targetFrontRunPoints
        )


        if activeTarget <= activeEntryPrice

            activeTarget := (
                 activeEntryPrice +
                 activeInitialRisk *
                 sourceRewardRisk
            )


    else

        activeInitialRisk := fixedStopLossPoints

        activeStop := (
             activeEntryPrice -
             fixedStopLossPoints
        )

        activeTarget := (
             activeEntryPrice +
             fixedTakeProfitPoints
        )


    pendingStopPrice := na

    pendingRiskDistance := na


// ============================================================================
// SECTION 43: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeEntryBar := bar_index

    breakEvenActivated := false


    if exitMode == "Source Range RR"

        activeStop := pendingStopPrice

        activeInitialRisk := (
             activeStop -
             activeEntryPrice
        )


        if (
             na(activeInitialRisk) or
             activeInitialRisk <= syminfo.mintick
        )

            activeInitialRisk := fixedStopLossPoints

            activeStop := (
                 activeEntryPrice +
                 activeInitialRisk
            )


        activeTarget := (
             activeEntryPrice -
             activeInitialRisk *
             sourceRewardRisk +
             targetFrontRunPoints
        )


        if activeTarget >= activeEntryPrice

            activeTarget := (
                 activeEntryPrice -
                 activeInitialRisk *
                 sourceRewardRisk
            )


    else

        activeInitialRisk := fixedStopLossPoints

        activeStop := (
             activeEntryPrice +
             fixedStopLossPoints
        )

        activeTarget := (
             activeEntryPrice -
             fixedTakeProfitPoints
        )


    pendingStopPrice := na

    pendingRiskDistance := na


// ============================================================================
// SECTION 44: LONG BREAK EVEN
// ============================================================================

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

    float longBETrigger = (
         activeEntryPrice +
         activeInitialRisk *
         breakEvenTriggerR
    )


    if high >= longBETrigger

        float newStop = (
             activeEntryPrice +
             breakEvenOffsetPoints
        )


        if (
             na(activeStop) or
             newStop > activeStop
        )

            activeStop := newStop


        breakEvenActivated := true


// ============================================================================
// SECTION 45: SHORT BREAK EVEN
// ============================================================================

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

    float shortBETrigger = (
         activeEntryPrice -
         activeInitialRisk *
         breakEvenTriggerR
    )


    if low <= shortBETrigger

        float newStop = (
             activeEntryPrice -
             breakEvenOffsetPoints
        )


        if (
             na(activeStop) or
             newStop < activeStop
        )

            activeStop := newStop


        breakEvenActivated := true


// ============================================================================
// SECTION 46: STANDARD LONG EXIT
// ============================================================================

if (
     inLong and
     not useTargetExtension and
     not na(activeStop) and
     not na(activeTarget)
)

    strategy.exit(
         "LX",
         from_entry="Long",
         stop=activeStop,
         limit=activeTarget,
         alert_profit="EXIT_LONG_TP_CNPS33",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS33" : "EXIT_LONG_SL_CNPS33",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 47: STANDARD SHORT EXIT
// ============================================================================

if (
     inShort and
     not useTargetExtension and
     not na(activeStop) and
     not na(activeTarget)
)

    strategy.exit(
         "SX",
         from_entry="Short",
         stop=activeStop,
         limit=activeTarget,
         alert_profit="EXIT_SHORT_TP_CNPS33",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS33" : "EXIT_SHORT_SL_CNPS33",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 48: SOURCE-LIKE LONG TARGET EXTENSION
// ============================================================================

float extensionTrailTicks = (
     extensionTrailPoints /
     syminfo.mintick
)


if (
     inLong and
     useTargetExtension and
     not na(activeStop) and
     not na(activeTarget)
)

    strategy.exit(
         "LX Trail",
         from_entry="Long",
         stop=activeStop,
         trail_price=activeTarget,
         trail_offset=extensionTrailTicks,
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS33" : "EXIT_LONG_SL_CNPS33",
         alert_trailing="EXIT_LONG_TRAIL_CNPS33",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 49: SOURCE-LIKE SHORT TARGET EXTENSION
// ============================================================================

if (
     inShort and
     useTargetExtension and
     not na(activeStop) and
     not na(activeTarget)
)

    strategy.exit(
         "SX Trail",
         from_entry="Short",
         stop=activeStop,
         trail_price=activeTarget,
         trail_offset=extensionTrailTicks,
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS33" : "EXIT_SHORT_SL_CNPS33",
         alert_trailing="EXIT_SHORT_TRAIL_CNPS33",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 50: POSITION CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeInitialRisk := na

    activeStop := na

    activeTarget := na

    activeEntryBar := na

    breakEvenActivated := false


// ============================================================================
// SECTION 51: FORCE CLOSE
// ============================================================================

bool forceCloseBar = (
     currentMinute < forceCloseTotal and
     currentCloseMinute >= forceCloseTotal
)


if forceCloseBar

    lookingForFirstBreakout := false

    lookingForSecondLong := false

    lookingForSecondShort := false


    if strategy.position_size != 0

        strategy.close_all(
             immediately=true,
             comment="EOD",
             alert_message="SESSION_END_CNPS33"
        )


// ============================================================================
// SECTION 52: RANGE MIDPOINT
// ============================================================================

float rangeMidpoint = na


if (
     not na(rangeHigh) and
     not na(rangeLow)
)

    rangeMidpoint := (
         rangeHigh +
         rangeLow
    ) / 2.0


// ============================================================================
// SECTION 53: RANGE VISUAL
// ============================================================================

plot(
     showRange and rangeReady
     ? rangeHigh
     : na,
     title="Opening Range High",
     color=color.blue,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showRange and rangeReady
     ? rangeMidpoint
     : na,
     title="Opening Range Mid",
     color=color.new(color.blue, 65),
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showRange and rangeReady
     ? rangeLow
     : na,
     title="Opening Range Low",
     color=color.blue,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 54: ARMED SECOND-BREAKOUT LEVEL
// ============================================================================

plot(
     showBreakoutLevel and lookingForSecondLong
     ? firstBreakoutHigh
     : na,
     title="Long Second Breakout Level",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showBreakoutLevel and lookingForSecondShort
     ? firstBreakoutLow
     : na,
     title="Short Second Breakout Level",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 55: ACTIVE SL / TP
// ============================================================================

plot(
     showActiveSLTP and strategy.position_size != 0
     ? activeStop
     : na,
     title="Active Stop",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showActiveSLTP and strategy.position_size != 0
     ? activeTarget
     : na,
     title="Active Target",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 56: SIGNAL MARKERS
// ============================================================================

plotshape(
     showSignals and firstBullBreak,
     title="First Bull Break",
     style=shape.circle,
     location=location.belowbar,
     color=color.orange,
     size=size.tiny,
     text="1"
)


plotshape(
     showSignals and firstBearBreak,
     title="First Bear Break",
     style=shape.circle,
     location=location.abovebar,
     color=color.orange,
     size=size.tiny,
     text="1"
)


plotshape(
     showSignals and finalLongSignal,
     title="Second Bull Break Entry",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.small,
     text="2",
     textcolor=color.white
)


plotshape(
     showSignals and finalShortSignal,
     title="Second Bear Break Entry",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.small,
     text="2",
     textcolor=color.white
)


// ============================================================================
// SECTION 57: DIAGNOSTICS
// ============================================================================

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


if showDiagnostics and barstate.islast

    string stateText = "OFF"


    if lookingForFirstBreakout

        stateText := "WAIT 1ST BREAK"


    else if lookingForSecondLong

        stateText := "WAIT 2ND LONG"


    else if lookingForSecondShort

        stateText := "WAIT 2ND SHORT"


    string positionText = "FLAT"


    if inLong

        positionText := "LONG"


    else if inShort

        positionText := "SHORT"


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


    table.cell(diagnostics, 0, 1, "State")
    table.cell(diagnostics, 1, 1, stateText)


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


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


    table.cell(diagnostics, 0, 4, "2nd Long Level")
    table.cell(diagnostics, 1, 4, str.tostring(firstBreakoutHigh, format.mintick))


    table.cell(diagnostics, 0, 5, "2nd Short Level")
    table.cell(diagnostics, 1, 5, str.tostring(firstBreakoutLow, format.mintick))


    table.cell(diagnostics, 0, 6, "1st Bull BO")
    table.cell(diagnostics, 1, 6, str.tostring(firstBullBreakCount))


    table.cell(diagnostics, 0, 7, "1st Bear BO")
    table.cell(diagnostics, 1, 7, str.tostring(firstBearBreakCount))


    table.cell(diagnostics, 0, 8, "2nd Bull BO")
    table.cell(diagnostics, 1, 8, str.tostring(secondBullBreakCount))


    table.cell(diagnostics, 0, 9, "2nd Bear BO")
    table.cell(diagnostics, 1, 9, str.tostring(secondBearBreakCount))


    table.cell(diagnostics, 0, 10, "Skipped")
    table.cell(diagnostics, 1, 10, str.tostring(skippedSetupsToday) + " / " + str.tostring(skipFirstSetups))


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


    table.cell(diagnostics, 0, 12, "Pros Long Risk")
    table.cell(diagnostics, 1, 12, str.tostring(prospectiveLongRisk, "0.00"))


    table.cell(diagnostics, 0, 13, "Pros Short Risk")
    table.cell(diagnostics, 1, 13, str.tostring(prospectiveShortRisk, "0.00"))


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


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


    table.cell(diagnostics, 0, 16, "TP Extend")
    table.cell(diagnostics, 1, 16, useTargetExtension ? "ON" : "OFF")


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