//@version=6
strategy(
     title="CNPS31 V2 EMA RSI PULLBACK CONFIRMED",
     shorttitle="CNPS31 V2",
     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
)


// ============================================================================
// CNPS31 V2 EMA RSI PULLBACK CONFIRMED
//
// SOURCE IDEA:
// EMA + RSI regime transition.
//
// V2 EXECUTION:
//
// Bull regime transition
//      ↓
// ARM LONG
//      ↓
// Wait for pullback toward EMA
//      ↓
// EMA holds
// + RSI remains bullish
// + EMA slope bullish
// + bullish candle
// + close breaks previous high
//      ↓
// LONG
//
// Short = symmetric.
//
// COST-AWARE:
// - Maximum 3 trades/day
// - Cooldown after exit
// - No automatic reversal
// - Structural SL must be inside an acceptable range
// - Default TP = 2R
//
// CNPS:
// 100M VND
// 3 contracts
// Commission 10,000 / contract
// Slippage 3 ticks
// ============================================================================


// ============================================================================
// SECTION 1: CORE SIGNAL
// ============================================================================

emaLength = input.int(
     9,
     title="EMA Length",
     minval=1,
     group="Core Signal"
)

rsiLength = input.int(
     14,
     title="RSI Length",
     minval=1,
     group="Core Signal"
)

rsiReferenceLevel = input.float(
     50.0,
     title="Source RSI Reference",
     minval=0.0,
     maxval=100.0,
     step=0.5,
     group="Core Signal"
)


// ============================================================================
// SECTION 2: ENTRY MODE
// ============================================================================

entryMode = input.string(
     "Pullback Confirmed",
     title="Entry Mode",
     options=[
         "Pullback Confirmed",
         "Continuation Confirmed",
         "Source Edge"
     ],
     group="Entry Logic"
)


// ============================================================================
// SECTION 3: SETUP LIFETIME
// ============================================================================

setupLifetimeBars = input.int(
     8,
     title="Setup Lifetime Bars",
     minval=1,
     maxval=50,
     group="Entry Logic"
)


// ============================================================================
// SECTION 4: RSI CONFIRMATION
// ============================================================================

longRSIConfirmation = input.float(
     52.0,
     title="Long RSI Confirmation",
     minval=50.0,
     maxval=80.0,
     step=0.5,
     group="Entry Quality"
)

shortRSIConfirmation = input.float(
     48.0,
     title="Short RSI Confirmation",
     minval=20.0,
     maxval=50.0,
     step=0.5,
     group="Entry Quality"
)


// ============================================================================
// SECTION 5: EMA RETEST
// ============================================================================

atrLength = input.int(
     14,
     title="ATR Length",
     minval=1,
     group="Entry Quality"
)

retestToleranceATR = input.float(
     0.20,
     title="EMA Retest Tolerance x ATR",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Entry Quality"
)

maximumCloseThroughEMAATR = input.float(
     0.10,
     title="Maximum Close Through EMA x ATR",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Entry Quality"
)


// ============================================================================
// SECTION 6: EMA SLOPE
// ============================================================================

emaSlopeLookback = input.int(
     3,
     title="EMA Slope Lookback",
     minval=1,
     maxval=20,
     group="Entry Quality"
)

requireEMASlope = input.bool(
     true,
     title="Require EMA Slope Alignment?",
     group="Entry Quality"
)


// ============================================================================
// SECTION 7: CANDLE CONFIRMATION
// ============================================================================

minimumBodyRatio = input.float(
     0.45,
     title="Minimum Confirmation Body / Range",
     minval=0.1,
     maxval=1.0,
     step=0.05,
     group="Entry Quality"
)

requirePreviousHighLowBreak = input.bool(
     true,
     title="Require Previous High / Low Break?",
     group="Entry Quality"
)


// ============================================================================
// SECTION 8: OVEREXTENSION
// ============================================================================

maximumExtensionATR = input.float(
     1.25,
     title="Maximum Distance From EMA x ATR",
     minval=0.25,
     maxval=5.0,
     step=0.05,
     group="Entry Quality"
)


// ============================================================================
// SECTION 9: 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=20,
     group="Trade Control"
)

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


// ============================================================================
// SECTION 10: EXIT MODE
// ============================================================================

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


// ============================================================================
// SECTION 11: STRUCTURE RISK
// ============================================================================

structureStopLookback = input.int(
     4,
     title="Structure Stop Lookback",
     minval=1,
     maxval=20,
     group="Structure Risk"
)

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

minimumStructureRisk = input.float(
     2.5,
     title="Minimum Structure Risk",
     minval=0.5,
     step=0.5,
     group="Structure Risk"
)

maximumStructureRisk = input.float(
     6.0,
     title="Maximum Structure Risk",
     minval=1.0,
     step=0.5,
     group="Structure Risk"
)

rewardRisk = input.float(
     2.0,
     title="Reward / Risk",
     minval=1.0,
     maxval=5.0,
     step=0.25,
     group="Structure Risk"
)


// ============================================================================
// SECTION 12: FIXED EXIT
// ============================================================================

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

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


// ============================================================================
// SECTION 13: OPPOSITE REGIME EXIT
// ============================================================================

exitOnOppositeRegime = input.bool(
     true,
     title="Exit On Opposite EMA + RSI Regime?",
     group="Signal Exit"
)


// ============================================================================
// SECTION 14: 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",
     minval=0.0,
     step=0.5,
     group="Break Even"
)


// ============================================================================
// SECTION 15: SESSION
// ============================================================================

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

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

morningStartMinute = input.int(
     5,
     title="Morning 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(
     20,
     title="Morning End Minute",
     minval=0,
     maxval=59,
     group="Session"
)

afternoonStartHour = input.int(
     13,
     title="Afternoon Start Hour",
     minval=0,
     maxval=23,
     group="Session"
)

afternoonStartMinute = input.int(
     5,
     title="Afternoon Start Minute",
     minval=0,
     maxval=59,
     group="Session"
)

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

lastEntryMinute = input.int(
     20,
     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 16: ALERTS
// ============================================================================

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


// ============================================================================
// SECTION 17: DISPLAY
// ============================================================================

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

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

showSetupMarkers = input.bool(
     false,
     title="Show Armed Setups?",
     group="Display"
)

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

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


// ============================================================================
// SECTION 18: CALCULATIONS
// ============================================================================

float emaValue = ta.ema(
     close,
     emaLength
)

float rsiValue = ta.rsi(
     close,
     rsiLength
)

float atrValue = ta.atr(
     atrLength
)


// ============================================================================
// SECTION 19: SOURCE CONDITIONS
// ============================================================================

bool bullishCondition = (
     close > emaValue and
     rsiValue > rsiReferenceLevel
)

bool bearishCondition = (
     close < emaValue and
     rsiValue < rsiReferenceLevel
)


// ============================================================================
// SECTION 20: SOURCE RISING EDGE
// ============================================================================

bool rawBuySignal = (
     bullishCondition and
     not bullishCondition[1] and
     barstate.isconfirmed
)

bool rawSellSignal = (
     bearishCondition and
     not bearishCondition[1] and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 21: CANDLE QUALITY
// ============================================================================

float candleRange = high - low

float candleBody = math.abs(close - open)

float bodyRatio = 0.0

if candleRange > 0
    bodyRatio := candleBody / candleRange


bool bullishCandle = close > open

bool bearishCandle = close < open

bool bodyQualityOK = bodyRatio >= minimumBodyRatio


// ============================================================================
// SECTION 22: EMA SLOPE
// ============================================================================

bool bullishEMASlope = (
     not requireEMASlope or
     emaValue > emaValue[emaSlopeLookback]
)

bool bearishEMASlope = (
     not requireEMASlope or
     emaValue < emaValue[emaSlopeLookback]
)


// ============================================================================
// SECTION 23: OVEREXTENSION
// ============================================================================

float emaDistanceATR = 0.0

if atrValue > 0
    emaDistanceATR := math.abs(close - emaValue) / atrValue


bool extensionOK = emaDistanceATR <= maximumExtensionATR


// ============================================================================
// SECTION 24: TIME ENGINE
// ============================================================================

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

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

int morningStartTotal = morningStartHour * 60 + morningStartMinute

int morningEndTotal = morningEndHour * 60 + morningEndMinute

int afternoonStartTotal = afternoonStartHour * 60 + afternoonStartMinute

int lastEntryTotal = lastEntryHour * 60 + lastEntryMinute

int sessionEndTotal = sessionEndHour * 60 + sessionEndMinute


bool morningSession = (
     currentMinute >= morningStartTotal and
     currentMinute <= morningEndTotal
)

bool afternoonSession = (
     currentMinute >= afternoonStartTotal and
     currentMinute <= lastEntryTotal
)

bool entrySession = morningSession or afternoonSession


// ============================================================================
// SECTION 25: 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 26: 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 27: COOLDOWN
// ============================================================================

var int lastExitBar = na

if positionJustClosed
    lastExitBar := bar_index


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

bool tradeLimitOK = tradesToday < maximumTradesPerDay


// ============================================================================
// SECTION 28: ARMED SETUP STATE
// ============================================================================

var bool longArmed = false

var bool shortArmed = false

var int longSignalBar = na

var int shortSignalBar = na

var float longSignalHigh = na

var float shortSignalLow = na

var bool longPullbackSeen = false

var bool shortPullbackSeen = false

var float longPullbackLow = na

var float shortPullbackHigh = na


// ============================================================================
// SECTION 29: RESET SETUPS DAILY
// ============================================================================

if newTradingDay

    longArmed := false

    shortArmed := false

    longSignalBar := na

    shortSignalBar := na

    longSignalHigh := na

    shortSignalLow := na

    longPullbackSeen := false

    shortPullbackSeen := false

    longPullbackLow := na

    shortPullbackHigh := na


// ============================================================================
// SECTION 30: ARM LONG
// ============================================================================

if (
     rawBuySignal and
     flat and
     entrySession
)

    longArmed := true

    longSignalBar := bar_index

    longSignalHigh := high

    longPullbackSeen := false

    longPullbackLow := na


    shortArmed := false

    shortPullbackSeen := false

    shortPullbackHigh := na


// ============================================================================
// SECTION 31: ARM SHORT
// ============================================================================

if (
     rawSellSignal and
     flat and
     entrySession
)

    shortArmed := true

    shortSignalBar := bar_index

    shortSignalLow := low

    shortPullbackSeen := false

    shortPullbackHigh := na


    longArmed := false

    longPullbackSeen := false

    longPullbackLow := na


// ============================================================================
// SECTION 32: SETUP EXPIRATION
// ============================================================================

bool longSetupExpired = (
     longArmed and
     not na(longSignalBar) and
     bar_index - longSignalBar > setupLifetimeBars
)

bool shortSetupExpired = (
     shortArmed and
     not na(shortSignalBar) and
     bar_index - shortSignalBar > setupLifetimeBars
)


if longSetupExpired

    longArmed := false

    longPullbackSeen := false


if shortSetupExpired

    shortArmed := false

    shortPullbackSeen := false


// ============================================================================
// SECTION 33: SETUP INVALIDATION
// ============================================================================
//
// If the market fully establishes the opposite regime before entry,
// cancel the pending setup.
//
// ============================================================================

if longArmed and bearishCondition

    longArmed := false

    longPullbackSeen := false


if shortArmed and bullishCondition

    shortArmed := false

    shortPullbackSeen := false


// ============================================================================
// SECTION 34: LONG EMA RETEST
// ============================================================================

bool longRetestTouch = (
     longArmed and
     not na(longSignalBar) and
     bar_index > longSignalBar and
     low <= emaValue + atrValue * retestToleranceATR and
     close >= emaValue - atrValue * maximumCloseThroughEMAATR
)


if longRetestTouch

    longPullbackSeen := true


    if na(longPullbackLow)

        longPullbackLow := low


    else

        longPullbackLow := math.min(
             longPullbackLow,
             low
        )


// ============================================================================
// SECTION 35: SHORT EMA RETEST
// ============================================================================

bool shortRetestTouch = (
     shortArmed and
     not na(shortSignalBar) and
     bar_index > shortSignalBar and
     high >= emaValue - atrValue * retestToleranceATR and
     close <= emaValue + atrValue * maximumCloseThroughEMAATR
)


if shortRetestTouch

    shortPullbackSeen := true


    if na(shortPullbackHigh)

        shortPullbackHigh := high


    else

        shortPullbackHigh := math.max(
             shortPullbackHigh,
             high
        )


// ============================================================================
// SECTION 36: PRICE CONFIRMATION
// ============================================================================

bool longPreviousBreakOK = (
     not requirePreviousHighLowBreak or
     close > high[1]
)

bool shortPreviousBreakOK = (
     not requirePreviousHighLowBreak or
     close < low[1]
)


// ============================================================================
// SECTION 37: PULLBACK-CONFIRMED LONG
// ============================================================================

bool pullbackLongSignal = (
     longArmed and
     longPullbackSeen and
     bullishCondition and
     rsiValue >= longRSIConfirmation and
     bullishEMASlope and
     bullishCandle and
     bodyQualityOK and
     longPreviousBreakOK and
     extensionOK and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 38: PULLBACK-CONFIRMED SHORT
// ============================================================================

bool pullbackShortSignal = (
     shortArmed and
     shortPullbackSeen and
     bearishCondition and
     rsiValue <= shortRSIConfirmation and
     bearishEMASlope and
     bearishCandle and
     bodyQualityOK and
     shortPreviousBreakOK and
     extensionOK and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 39: CONTINUATION MODE
// ============================================================================

bool continuationLongSignal = (
     longArmed and
     not na(longSignalBar) and
     bar_index > longSignalBar and
     bullishCondition and
     rsiValue >= longRSIConfirmation and
     bullishEMASlope and
     bullishCandle and
     bodyQualityOK and
     not na(longSignalHigh) and
     close > longSignalHigh and
     extensionOK and
     barstate.isconfirmed
)


bool continuationShortSignal = (
     shortArmed and
     not na(shortSignalBar) and
     bar_index > shortSignalBar and
     bearishCondition and
     rsiValue <= shortRSIConfirmation and
     bearishEMASlope and
     bearishCandle and
     bodyQualityOK and
     not na(shortSignalLow) and
     close < shortSignalLow and
     extensionOK and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 40: RAW ENTRY BY MODE
// ============================================================================

bool rawLongEntry = false

bool rawShortEntry = false


if entryMode == "Source Edge"

    rawLongEntry := rawBuySignal

    rawShortEntry := rawSellSignal


else if entryMode == "Continuation Confirmed"

    rawLongEntry := continuationLongSignal

    rawShortEntry := continuationShortSignal


else

    rawLongEntry := pullbackLongSignal

    rawShortEntry := pullbackShortSignal


// ============================================================================
// SECTION 41: ENTRY APPROVAL
// ============================================================================

bool commonEntryApproval = (
     flat and
     entrySession and
     currentCloseMinute < sessionEndTotal and
     cooldownOK and
     tradeLimitOK and
     barstate.isconfirmed
)


bool preliminaryLong = (
     commonEntryApproval and
     allowLong and
     rawLongEntry
)


bool preliminaryShort = (
     commonEntryApproval and
     allowShort and
     rawShortEntry
)


// ============================================================================
// SECTION 42: PROSPECTIVE STRUCTURE STOP
// ============================================================================

float recentLowest = ta.lowest(
     low,
     structureStopLookback
)

float recentHighest = ta.highest(
     high,
     structureStopLookback
)


float prospectiveLongStop = recentLowest - structureStopBuffer

float prospectiveShortStop = recentHighest + structureStopBuffer


if (
     entryMode == "Pullback Confirmed" and
     not na(longPullbackLow)
)

    prospectiveLongStop := math.min(
         prospectiveLongStop,
         longPullbackLow - structureStopBuffer
    )


if (
     entryMode == "Pullback Confirmed" and
     not na(shortPullbackHigh)
)

    prospectiveShortStop := math.max(
         prospectiveShortStop,
         shortPullbackHigh + structureStopBuffer
    )


// ============================================================================
// SECTION 43: STRUCTURE RISK
// ============================================================================

float prospectiveLongRisk = close - prospectiveLongStop

float prospectiveShortRisk = prospectiveShortStop - close


bool longStructureRiskOK = (
     prospectiveLongRisk >= minimumStructureRisk and
     prospectiveLongRisk <= maximumStructureRisk
)


bool shortStructureRiskOK = (
     prospectiveShortRisk >= minimumStructureRisk and
     prospectiveShortRisk <= maximumStructureRisk
)


// ============================================================================
// SECTION 44: FINAL ENTRY SIGNALS
// ============================================================================

bool longCandidate = (
     preliminaryLong and
     (
         exitMode != "Structure RR" or
         longStructureRiskOK
     )
)


bool shortCandidate = (
     preliminaryShort and
     (
         exitMode != "Structure RR" or
         shortStructureRiskOK
     )
)


bool conflictingSignal = (
     longCandidate and
     shortCandidate
)


bool finalLongSignal = (
     longCandidate and
     not conflictingSignal
)


bool finalShortSignal = (
     shortCandidate and
     not conflictingSignal
)


// ============================================================================
// SECTION 45: PENDING RISK
// ============================================================================

var float pendingStop = na

var float pendingRisk = na


// ============================================================================
// SECTION 46: LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingStop := prospectiveLongStop

    pendingRisk := prospectiveLongRisk


    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment="EMA RSI Pullback Long",
         alert_message="ENTRY_LONG_CNPS31_V2"
    )


    tradesToday += 1


    longArmed := false

    shortArmed := false

    longPullbackSeen := false

    shortPullbackSeen := false


// ============================================================================
// SECTION 47: SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingStop := prospectiveShortStop

    pendingRisk := prospectiveShortRisk


    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment="EMA RSI Pullback Short",
         alert_message="ENTRY_SHORT_CNPS31_V2"
    )


    tradesToday += 1


    longArmed := false

    shortArmed := false

    longPullbackSeen := false

    shortPullbackSeen := false


// ============================================================================
// SECTION 48: 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 bool breakEvenActivated = false


// ============================================================================
// SECTION 49: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeEntryBar := bar_index

    breakEvenActivated := false


    if exitMode == "Structure RR"

        float actualRisk = activeEntryPrice - pendingStop


        if na(actualRisk) or actualRisk <= 0

            actualRisk := fixedStopLossPoints


        activeInitialRisk := actualRisk

        activeLongSL := activeEntryPrice - actualRisk

        activeLongTP := activeEntryPrice + actualRisk * rewardRisk


    else

        activeInitialRisk := fixedStopLossPoints

        activeLongSL := activeEntryPrice - fixedStopLossPoints

        activeLongTP := activeEntryPrice + fixedTakeProfitPoints


    activeShortSL := na

    activeShortTP := na


    pendingStop := na

    pendingRisk := na


// ============================================================================
// SECTION 50: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeEntryBar := bar_index

    breakEvenActivated := false


    if exitMode == "Structure RR"

        float actualRisk = pendingStop - activeEntryPrice


        if na(actualRisk) or actualRisk <= 0

            actualRisk := fixedStopLossPoints


        activeInitialRisk := actualRisk

        activeShortSL := activeEntryPrice + actualRisk

        activeShortTP := activeEntryPrice - actualRisk * rewardRisk


    else

        activeInitialRisk := fixedStopLossPoints

        activeShortSL := activeEntryPrice + fixedStopLossPoints

        activeShortTP := activeEntryPrice - fixedTakeProfitPoints


    activeLongSL := na

    activeLongTP := na


    pendingStop := na

    pendingRisk := na


// ============================================================================
// SECTION 51: LONG BREAK EVEN
// ============================================================================

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

    float triggerPrice = activeEntryPrice + activeInitialRisk * breakEvenTriggerR


    if high >= triggerPrice

        float newStop = activeEntryPrice + breakEvenOffsetPoints


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


        breakEvenActivated := true


// ============================================================================
// SECTION 52: SHORT BREAK EVEN
// ============================================================================

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

    float triggerPrice = activeEntryPrice - activeInitialRisk * breakEvenTriggerR


    if low <= triggerPrice

        float newStop = activeEntryPrice - breakEvenOffsetPoints


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


        breakEvenActivated := true


// ============================================================================
// SECTION 53: LONG HARD EXIT
// ============================================================================

if (
     inLong and
     not na(activeLongSL) and
     not na(activeLongTP)
)

    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=activeLongSL,
         limit=activeLongTP,
         alert_profit="EXIT_LONG_TP_CNPS31_V2",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS31_V2" : "EXIT_LONG_SL_CNPS31_V2",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 54: SHORT HARD EXIT
// ============================================================================

if (
     inShort and
     not na(activeShortSL) and
     not na(activeShortTP)
)

    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=activeShortSL,
         limit=activeShortTP,
         alert_profit="EXIT_SHORT_TP_CNPS31_V2",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS31_V2" : "EXIT_SHORT_SL_CNPS31_V2",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 55: OPPOSITE REGIME EXIT
// ============================================================================

bool longOppositeExit = (
     inLong and
     exitOnOppositeRegime and
     rawSellSignal and
     not na(activeEntryBar) and
     bar_index > activeEntryBar
)


bool shortOppositeExit = (
     inShort and
     exitOnOppositeRegime and
     rawBuySignal and
     not na(activeEntryBar) and
     bar_index > activeEntryBar
)


if longOppositeExit

    strategy.close(
         "Long",
         comment="Opposite Regime",
         alert_message="REGIME_EXIT_LONG_CNPS31_V2"
    )


if shortOppositeExit

    strategy.close(
         "Short",
         comment="Opposite Regime",
         alert_message="REGIME_EXIT_SHORT_CNPS31_V2"
    )


// ============================================================================
// SECTION 56: CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeInitialRisk := na

    activeLongSL := na

    activeLongTP := na

    activeShortSL := na

    activeShortTP := na

    activeEntryBar := na

    breakEvenActivated := false


// ============================================================================
// SECTION 57: SESSION END
// ============================================================================

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


if finalSessionBar

    longArmed := false

    shortArmed := false


    if strategy.position_size != 0

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


// ============================================================================
// SECTION 58: EMA DISPLAY
// ============================================================================

plot(
     showEMA ? emaValue : na,
     title="EMA",
     color=color.blue,
     linewidth=2
)


// ============================================================================
// SECTION 59: 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 60: SETUP MARKERS
// ============================================================================

plotshape(
     showSetupMarkers and rawBuySignal,
     title="Long Regime Armed",
     style=shape.circle,
     location=location.belowbar,
     color=color.orange,
     size=size.tiny,
     text="A"
)


plotshape(
     showSetupMarkers and rawSellSignal,
     title="Short Regime Armed",
     style=shape.circle,
     location=location.abovebar,
     color=color.orange,
     size=size.tiny,
     text="A"
)


// ============================================================================
// SECTION 61: ENTRY MARKERS
// ============================================================================

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


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


// ============================================================================
// SECTION 62: DIAGNOSTIC COUNTERS
// ============================================================================

var int sourceLongCount = 0

var int sourceShortCount = 0

var int pullbackLongCount = 0

var int pullbackShortCount = 0

var int finalLongCount = 0

var int finalShortCount = 0


if rawBuySignal
    sourceLongCount += 1


if rawSellSignal
    sourceShortCount += 1


if pullbackLongSignal
    pullbackLongCount += 1


if pullbackShortSignal
    pullbackShortCount += 1


if finalLongSignal
    finalLongCount += 1


if finalShortSignal
    finalShortCount += 1


// ============================================================================
// SECTION 63: DIAGNOSTICS
// ============================================================================

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


if showDiagnostics and barstate.islast

    string regimeText = "NEUTRAL"


    if bullishCondition
        regimeText := "BULL"


    else if bearishCondition
        regimeText := "BEAR"


    string setupText = "NONE"


    if longArmed
        setupText := longPullbackSeen ? "LONG RETEST" : "LONG ARMED"


    else if shortArmed
        setupText := shortPullbackSeen ? "SHORT RETEST" : "SHORT ARMED"


    string positionText = "FLAT"


    if inLong
        positionText := "LONG"


    else if inShort
        positionText := "SHORT"


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


    table.cell(diagnostics, 0, 1, "Entry Mode")
    table.cell(diagnostics, 1, 1, entryMode)


    table.cell(diagnostics, 0, 2, "Regime")
    table.cell(diagnostics, 1, 2, regimeText)


    table.cell(diagnostics, 0, 3, "Setup")
    table.cell(diagnostics, 1, 3, setupText)


    table.cell(diagnostics, 0, 4, "EMA")
    table.cell(diagnostics, 1, 4, str.tostring(emaValue, format.mintick))


    table.cell(diagnostics, 0, 5, "RSI")
    table.cell(diagnostics, 1, 5, str.tostring(rsiValue, "0.0"))


    table.cell(diagnostics, 0, 6, "EMA Dist ATR")
    table.cell(diagnostics, 1, 6, str.tostring(emaDistanceATR, "0.00"))


    table.cell(diagnostics, 0, 7, "Source L / S")
    table.cell(diagnostics, 1, 7, str.tostring(sourceLongCount) + " / " + str.tostring(sourceShortCount))


    table.cell(diagnostics, 0, 8, "PB Confirm L / S")
    table.cell(diagnostics, 1, 8, str.tostring(pullbackLongCount) + " / " + str.tostring(pullbackShortCount))


    table.cell(diagnostics, 0, 9, "Entries L / S")
    table.cell(diagnostics, 1, 9, str.tostring(finalLongCount) + " / " + str.tostring(finalShortCount))


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


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


    table.cell(diagnostics, 0, 12, "Risk Range")
    table.cell(diagnostics, 1, 12, str.tostring(minimumStructureRisk, "0.0") + " - " + str.tostring(maximumStructureRisk, "0.0"))


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


    table.cell(diagnostics, 0, 14, "R:R")
    table.cell(diagnostics, 1, 14, str.tostring(rewardRisk, "0.00"))


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


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


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