//@version=6
strategy(
     title="CNPS39 V2 MACD SAR PULLBACK",
     shorttitle="CNPS39 V2 MSTF",
     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=100,
     max_lines_count=100,
     max_bars_back=5000
)


// ============================================================================
// CNPS39 V2 MACD SAR PULLBACK
//
// SOURCE CORE PRESERVED:
//
// LONG ARM EVENT:
//
// Close > EMA200
// + MACD > Signal
// + SAR < Close
// + MACD/SAR pair JUST changed from false to true
//
// SHORT:
// Mirror.
//
// V2 CHANGE:
//
// Source event does NOT enter immediately.
//
// SOURCE EVENT
//      ↓
// ARM DIRECTION
//      ↓
// WAIT FOR PULLBACK
//      ↓
// MACD + SAR STILL ALIGNED
// + EMA200 TREND STILL VALID
// + CONFIRMATION CANDLE
//      ↓
// ENTRY
//
// DEFAULT:
//
// Pullback Reference = EMA20
// Arm Life = 10 bars
// EMA200 slope required
// ADX optional OFF
// Candle body >= 35%
// Structural risk = 2.5 to 7 points
// TP = 2R
// Max 2 trades/day
// Cooldown 8 bars
//
// CNPS:
//
// Initial capital = 100M
// Fixed qty = 3 contracts
// Commission = 10,000 / contract
// Slippage = 3 ticks
// ============================================================================


// ============================================================================
// SECTION 1: SOURCE EMA
// ============================================================================

trendEMALength = input.int(
     200,
     title="Trend EMA Length",
     minval=10,
     maxval=1000,
     group="Source"
)


// ============================================================================
// SECTION 2: MACD
// ============================================================================

macdFastLength = input.int(
     12,
     title="MACD Fast",
     minval=2,
     maxval=200,
     group="Source"
)

macdSlowLength = input.int(
     26,
     title="MACD Slow",
     minval=3,
     maxval=400,
     group="Source"
)

macdSignalLength = input.int(
     9,
     title="MACD Signal",
     minval=2,
     maxval=100,
     group="Source"
)


// ============================================================================
// SECTION 3: PARABOLIC SAR
// ============================================================================

sarStart = input.float(
     0.02,
     title="SAR Start",
     minval=0.001,
     maxval=1.0,
     step=0.01,
     group="Source"
)

sarIncrement = input.float(
     0.02,
     title="SAR Increment",
     minval=0.001,
     maxval=1.0,
     step=0.01,
     group="Source"
)

sarMaximum = input.float(
     0.20,
     title="SAR Maximum",
     minval=0.01,
     maxval=1.0,
     step=0.01,
     group="Source"
)


// ============================================================================
// SECTION 4: SOURCE TRIGGER ROUTE
// ============================================================================

sourceTriggerMode = input.string(
     "Both",
     title="Source Trigger Route",
     options=[
         "Both",
         "MACD Cross Only",
         "SAR Flip Only"
     ],
     group="Source"
)


// ============================================================================
// SECTION 5: PULLBACK
// ============================================================================

pullbackReference = input.string(
     "EMA20",
     title="Pullback Reference",
     options=[
         "EMA20",
         "Signal Candle Midpoint"
     ],
     group="Pullback"
)

pullbackEMALength = input.int(
     20,
     title="Pullback EMA Length",
     minval=2,
     maxval=200,
     group="Pullback"
)

armLifeBars = input.int(
     10,
     title="Maximum Bars To Wait",
     minval=1,
     maxval=100,
     group="Pullback"
)

pullbackToleranceATR = input.float(
     0.20,
     title="Pullback Tolerance x ATR",
     minval=0.0,
     maxval=2.0,
     step=0.05,
     group="Pullback"
)


// ============================================================================
// SECTION 6: EMA200 QUALITY
// ============================================================================

requireEMA200Slope = input.bool(
     true,
     title="Require EMA200 Slope?",
     group="Entry Quality"
)

emaSlopeLookback = input.int(
     5,
     title="EMA200 Slope Lookback",
     minval=1,
     maxval=50,
     group="Entry Quality"
)


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

requireDirectionalCandle = input.bool(
     true,
     title="Require Directional Candle?",
     group="Entry Quality"
)

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

requirePreviousBreak = input.bool(
     false,
     title="Require Close Beyond Previous High / Low?",
     group="Entry Quality"
)


// ============================================================================
// SECTION 8: OPTIONAL ADX
// ============================================================================

useADXFilter = input.bool(
     false,
     title="Use ADX / DI Filter?",
     group="Entry Quality"
)

adxLength = input.int(
     14,
     title="ADX Length",
     minval=2,
     maxval=100,
     group="Entry Quality"
)

adxThreshold = input.float(
     18.0,
     title="Minimum ADX",
     minval=1.0,
     step=1.0,
     group="Entry Quality"
)

requireDIDirection = input.bool(
     true,
     title="Require DI Direction?",
     group="Entry Quality"
)


// ============================================================================
// SECTION 9: 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"
)


// ============================================================================
// SECTION 10: RISK
// ============================================================================

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

structureLookback = input.int(
     5,
     title="Structure Stop Lookback",
     minval=2,
     maxval=30,
     group="Risk Management"
)

stopBufferATR = input.float(
     0.10,
     title="Stop Buffer x ATR",
     minval=0.0,
     maxval=2.0,
     step=0.05,
     group="Risk Management"
)

minimumRiskPoints = input.float(
     2.5,
     title="Minimum Risk Points",
     minval=0.5,
     step=0.5,
     group="Risk Management"
)

maximumRiskPoints = input.float(
     7.0,
     title="Maximum Risk Points",
     minval=1.0,
     step=0.5,
     group="Risk Management"
)

rewardRisk = input.float(
     2.0,
     title="Take Profit R",
     minval=1.0,
     maxval=5.0,
     step=0.25,
     group="Risk Management"
)


// ============================================================================
// SECTION 11: OPTIONAL BREAK EVEN
// ============================================================================

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

breakEvenTriggerR = input.float(
     1.25,
     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 12: OPTIONAL STATE FAILURE EXIT
// ============================================================================

exitOnPairFailure = input.bool(
     false,
     title="Exit If MACD + SAR Pair Fails?",
     group="Risk Management"
)


// ============================================================================
// SECTION 13: TIME EXIT
// ============================================================================

maximumBarsInTrade = input.int(
     90,
     title="Maximum Bars In Trade (0 = Off)",
     minval=0,
     maxval=500,
     group="Risk Management"
)


// ============================================================================
// SECTION 14: TRADE CONTROL
// ============================================================================

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

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


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

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

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


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

showEMA200 = input.bool(
     true,
     title="Show EMA200?",
     group="Display"
)

showPullbackEMA = input.bool(
     false,
     title="Show Pullback EMA?",
     group="Display"
)

showSAR = input.bool(
     false,
     title="Show SAR?",
     group="Display"
)

showArmMarkers = input.bool(
     false,
     title="Show Source Arm Markers?",
     group="Display"
)

showEntryMarkers = 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 18: CORE INDICATORS
// ============================================================================

float trendEMA = ta.ema(
     close,
     trendEMALength
)

float pullbackEMA = ta.ema(
     close,
     pullbackEMALength
)

[
     macdValue,
     macdSignal,
     macdHistogram
] = ta.macd(
     close,
     macdFastLength,
     macdSlowLength,
     macdSignalLength
)

float sarValue = ta.sar(
     sarStart,
     sarIncrement,
     sarMaximum
)

float atrValue = ta.atr(
     atrLength
)

[
     diPlus,
     diMinus,
     adxValue
] = ta.dmi(
     adxLength,
     adxLength
)


// ============================================================================
// SECTION 19: SOURCE STATES
// ============================================================================

bool trendUp = (
     close > trendEMA
)

bool trendDown = (
     close < trendEMA
)

bool macdUp = (
     macdValue > macdSignal
)

bool macdDown = (
     macdValue < macdSignal
)

bool sarBelow = (
     sarValue < close
)

bool sarAbove = (
     sarValue > close
)

bool pairUp = (
     macdUp and
     sarBelow
)

bool pairDown = (
     macdDown and
     sarAbove
)


// ============================================================================
// SECTION 20: SOURCE PAIR EDGE
// ============================================================================

bool pairUpEvent = (
     pairUp and
     not pairUp[1]
)

bool pairDownEvent = (
     pairDown and
     not pairDown[1]
)


// ============================================================================
// SECTION 21: SOURCE TRIGGER ROUTE
// ============================================================================

bool macdBullCross = (
     macdUp and
     not macdUp[1]
)

bool macdBearCross = (
     macdDown and
     not macdDown[1]
)

bool sarBullFlip = (
     sarBelow and
     not sarBelow[1]
)

bool sarBearFlip = (
     sarAbove and
     not sarAbove[1]
)


int longTrigger = 0

int shortTrigger = 0


if pairUpEvent

    if macdBullCross
        longTrigger := 1
    else
        longTrigger := 2


if pairDownEvent

    if macdBearCross
        shortTrigger := 1
    else
        shortTrigger := 2


// ============================================================================
// SECTION 22: ROUTE FILTER
// ============================================================================

bool longRouteOK = true

bool shortRouteOK = true


if sourceTriggerMode == "MACD Cross Only"

    longRouteOK := longTrigger == 1

    shortRouteOK := shortTrigger == 1


else if sourceTriggerMode == "SAR Flip Only"

    longRouteOK := longTrigger == 2

    shortRouteOK := shortTrigger == 2


// ============================================================================
// SECTION 23: EXACT SOURCE ARM EVENTS
// ============================================================================

bool sourceLongArm = (
     barstate.isconfirmed and
     trendUp and
     pairUpEvent and
     longRouteOK
)


bool sourceShortArm = (
     barstate.isconfirmed and
     trendDown and
     pairDownEvent and
     shortRouteOK
)


// ============================================================================
// SECTION 24: ARM STATE
// ============================================================================

var bool longArmed = false

var bool shortArmed = false

var int longArmBar = na

var int shortArmBar = na

var float longSignalMidpoint = na

var float shortSignalMidpoint = na


if sourceLongArm

    longArmed := true

    shortArmed := false

    longArmBar := bar_index

    shortArmBar := na

    longSignalMidpoint := (high + low) / 2.0

    shortSignalMidpoint := na


if sourceShortArm

    shortArmed := true

    longArmed := false

    shortArmBar := bar_index

    longArmBar := na

    shortSignalMidpoint := (high + low) / 2.0

    longSignalMidpoint := na


// ============================================================================
// SECTION 25: ARM EXPIRATION
// ============================================================================

if (
     longArmed and
     not na(longArmBar) and
     bar_index - longArmBar > armLifeBars
)

    longArmed := false

    longArmBar := na

    longSignalMidpoint := na


if (
     shortArmed and
     not na(shortArmBar) and
     bar_index - shortArmBar > armLifeBars
)

    shortArmed := false

    shortArmBar := na

    shortSignalMidpoint := na


// ============================================================================
// SECTION 26: ARM INVALIDATION
// ============================================================================
//
// Neutral is allowed.
//
// Full opposite source alignment invalidates the armed setup.
//
// ============================================================================

if (
     longArmed and
     trendDown and
     pairDown
)

    longArmed := false

    longArmBar := na

    longSignalMidpoint := na


if (
     shortArmed and
     trendUp and
     pairUp
)

    shortArmed := false

    shortArmBar := na

    shortSignalMidpoint := na


// ============================================================================
// SECTION 27: EMA200 SLOPE
// ============================================================================

bool longSlopeOK = (
     not requireEMA200Slope or
     trendEMA > trendEMA[emaSlopeLookback]
)


bool shortSlopeOK = (
     not requireEMA200Slope or
     trendEMA < trendEMA[emaSlopeLookback]
)


// ============================================================================
// SECTION 28: PULLBACK REFERENCE
// ============================================================================

float longPullbackReference = pullbackEMA

float shortPullbackReference = pullbackEMA


if pullbackReference == "Signal Candle Midpoint"

    longPullbackReference := longSignalMidpoint

    shortPullbackReference := shortSignalMidpoint


float pullbackTolerance = (
     atrValue *
     pullbackToleranceATR
)


// ============================================================================
// SECTION 29: PULLBACK TOUCH
// ============================================================================

bool longPullbackTouch = (
     not na(longPullbackReference) and
     low <= longPullbackReference + pullbackTolerance
)


bool shortPullbackTouch = (
     not na(shortPullbackReference) and
     high >= shortPullbackReference - pullbackTolerance
)


// ============================================================================
// SECTION 30: PULLBACK RECLAIM
// ============================================================================

bool longReclaim = (
     not na(longPullbackReference) and
     close > longPullbackReference
)


bool shortReclaim = (
     not na(shortPullbackReference) and
     close < shortPullbackReference
)


// ============================================================================
// SECTION 31: SOURCE STATE STILL VALID
// ============================================================================

bool longSourceStateOK = (
     trendUp and
     pairUp
)


bool shortSourceStateOK = (
     trendDown and
     pairDown
)


// ============================================================================
// SECTION 32: CANDLE QUALITY
// ============================================================================

float candleRange = math.max(
     high - low,
     syminfo.mintick
)

float candleBody = math.abs(
     close - open
)

float bodyRatio = (
     candleBody /
     candleRange
)


bool longCandleOK = (
     bodyRatio >= minimumBodyRatio and
     (
         not requireDirectionalCandle or
         close > open
     ) and
     (
         not requirePreviousBreak or
         close > high[1]
     )
)


bool shortCandleOK = (
     bodyRatio >= minimumBodyRatio and
     (
         not requireDirectionalCandle or
         close < open
     ) and
     (
         not requirePreviousBreak or
         close < low[1]
     )
)


// ============================================================================
// SECTION 33: ADX QUALITY
// ============================================================================

bool longADXOK = true

bool shortADXOK = true


if useADXFilter

    longADXOK := (
         adxValue >= adxThreshold and
         (
             not requireDIDirection or
             diPlus > diMinus
         )
    )


    shortADXOK := (
         adxValue >= adxThreshold and
         (
             not requireDIDirection or
             diMinus > diPlus
         )
    )


// ============================================================================
// SECTION 34: RAW V2 SETUPS
// ============================================================================

bool rawLongSetup = (
     longArmed and
     not na(longArmBar) and
     bar_index > longArmBar and
     longPullbackTouch and
     longReclaim and
     longSourceStateOK and
     longSlopeOK and
     longCandleOK and
     longADXOK and
     barstate.isconfirmed
)


bool rawShortSetup = (
     shortArmed and
     not na(shortArmBar) and
     bar_index > shortArmBar and
     shortPullbackTouch and
     shortReclaim and
     shortSourceStateOK and
     shortSlopeOK and
     shortCandleOK and
     shortADXOK and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 35: STRUCTURAL STOP
// ============================================================================

float recentLow = ta.lowest(
     low,
     structureLookback
)

float recentHigh = ta.highest(
     high,
     structureLookback
)


float prospectiveLongStop = (
     recentLow -
     atrValue *
     stopBufferATR
)

float prospectiveShortStop = (
     recentHigh +
     atrValue *
     stopBufferATR
)


float prospectiveLongRisk = (
     close -
     prospectiveLongStop
)

float prospectiveShortRisk = (
     prospectiveShortStop -
     close
)


// ============================================================================
// SECTION 36: RISK FILTER
// ============================================================================

bool longRiskOK = (
     prospectiveLongRisk >= minimumRiskPoints and
     prospectiveLongRisk <= maximumRiskPoints
)


bool shortRiskOK = (
     prospectiveShortRisk >= minimumRiskPoints and
     prospectiveShortRisk <= maximumRiskPoints
)


// ============================================================================
// SECTION 37: SESSION
// ============================================================================

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 forceCloseTotal = (
     forceCloseHour *
     60 +
     forceCloseMinute
)


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


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


bool entrySession = (
     morningSession or
     afternoonSession
)


// ============================================================================
// SECTION 38: 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

    longArmed := false

    shortArmed := false

    longArmBar := na

    shortArmBar := na

    longSignalMidpoint := na

    shortSignalMidpoint := na


// ============================================================================
// SECTION 39: 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 40: COOLDOWN
// ============================================================================

var int lastExitBar = na


if positionJustClosed

    lastExitBar := bar_index


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


// ============================================================================
// SECTION 41: TRADE LIMIT
// ============================================================================

bool tradeLimitOK = (
     tradesToday <
     maximumTradesPerDay
)


// ============================================================================
// SECTION 42: ENTRY APPROVAL
// ============================================================================

bool commonEntryOK = (
     flat and
     entrySession and
     currentCloseMinute < forceCloseTotal and
     cooldownOK and
     tradeLimitOK and
     barstate.isconfirmed
)


bool finalLongSignal = (
     commonEntryOK and
     allowLong and
     rawLongSetup and
     longRiskOK
)


bool finalShortSignal = (
     commonEntryOK and
     allowShort and
     rawShortSetup and
     shortRiskOK and
     not finalLongSignal
)


// ============================================================================
// SECTION 43: PENDING STOP
// ============================================================================

var float pendingStop = na


// ============================================================================
// SECTION 44: LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingStop := (
         prospectiveLongStop
    )


    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment="MSTF PB L",
         alert_message="ENTRY_LONG_CNPS39_V2"
    )


    tradesToday += 1

    longArmed := false

    longArmBar := na

    longSignalMidpoint := na


// ============================================================================
// SECTION 45: SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingStop := (
         prospectiveShortStop
    )


    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment="MSTF PB S",
         alert_message="ENTRY_SHORT_CNPS39_V2"
    )


    tradesToday += 1

    shortArmed := false

    shortArmBar := na

    shortSignalMidpoint := na


// ============================================================================
// SECTION 46: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeInitialRisk = na

var float activeStop = na

var float activeTarget = na

var int activeEntryBar = na

var bool breakEvenActivated = false


// ============================================================================
// SECTION 47: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := (
         strategy.position_avg_price
    )

    activeEntryBar := bar_index

    activeStop := pendingStop

    breakEvenActivated := false


    if (
         na(activeStop) or
         activeStop >= activeEntryPrice
    )

        activeStop := (
             activeEntryPrice -
             minimumRiskPoints
        )


    activeInitialRisk := (
         activeEntryPrice -
         activeStop
    )

    activeTarget := (
         activeEntryPrice +
         activeInitialRisk *
         rewardRisk
    )


    pendingStop := na


// ============================================================================
// SECTION 48: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := (
         strategy.position_avg_price
    )

    activeEntryBar := bar_index

    activeStop := pendingStop

    breakEvenActivated := false


    if (
         na(activeStop) or
         activeStop <= activeEntryPrice
    )

        activeStop := (
             activeEntryPrice +
             minimumRiskPoints
        )


    activeInitialRisk := (
         activeStop -
         activeEntryPrice
    )

    activeTarget := (
         activeEntryPrice -
         activeInitialRisk *
         rewardRisk
    )


    pendingStop := na


// ============================================================================
// SECTION 49: BREAK EVEN LONG
// ============================================================================

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

    float triggerPrice = (
         activeEntryPrice +
         activeInitialRisk *
         breakEvenTriggerR
    )


    if high >= triggerPrice

        float candidateStop = (
             activeEntryPrice +
             breakEvenOffsetPoints
        )


        activeStop := math.max(
             activeStop,
             candidateStop
        )


        breakEvenActivated := true


// ============================================================================
// SECTION 50: BREAK EVEN SHORT
// ============================================================================

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

    float triggerPrice = (
         activeEntryPrice -
         activeInitialRisk *
         breakEvenTriggerR
    )


    if low <= triggerPrice

        float candidateStop = (
             activeEntryPrice -
             breakEvenOffsetPoints
        )


        activeStop := math.min(
             activeStop,
             candidateStop
        )


        breakEvenActivated := true


// ============================================================================
// SECTION 51: LONG PROTECTIVE EXIT
// ============================================================================

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

    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=activeStop,
         limit=activeTarget,
         alert_profit="EXIT_LONG_TP_CNPS39_V2",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS39_V2" : "EXIT_LONG_SL_CNPS39_V2",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 52: SHORT PROTECTIVE EXIT
// ============================================================================

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

    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=activeStop,
         limit=activeTarget,
         alert_profit="EXIT_SHORT_TP_CNPS39_V2",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS39_V2" : "EXIT_SHORT_SL_CNPS39_V2",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 53: OPTIONAL PAIR FAILURE EXIT
// ============================================================================

bool longPairFailure = (
     inLong and
     exitOnPairFailure and
     not pairUp and
     not na(activeEntryBar) and
     bar_index > activeEntryBar and
     barstate.isconfirmed
)


bool shortPairFailure = (
     inShort and
     exitOnPairFailure and
     not pairDown and
     not na(activeEntryBar) and
     bar_index > activeEntryBar and
     barstate.isconfirmed
)


if longPairFailure

    strategy.close(
         "Long",
         comment="Pair Fail",
         alert_message="PAIR_FAIL_LONG_CNPS39_V2"
    )


if shortPairFailure

    strategy.close(
         "Short",
         comment="Pair Fail",
         alert_message="PAIR_FAIL_SHORT_CNPS39_V2"
    )


// ============================================================================
// SECTION 54: TIME EXIT
// ============================================================================

if (
     maximumBarsInTrade > 0 and
     strategy.position_size != 0 and
     not na(activeEntryBar)
)

    int barsInTrade = (
         bar_index -
         activeEntryBar
    )


    if barsInTrade >= maximumBarsInTrade

        strategy.close_all(
             comment="Time Exit",
             alert_message="TIME_EXIT_CNPS39_V2"
        )


// ============================================================================
// SECTION 55: CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeInitialRisk := na

    activeStop := na

    activeTarget := na

    activeEntryBar := na

    breakEvenActivated := false


// ============================================================================
// SECTION 56: FORCE CLOSE
// ============================================================================

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


if forceCloseBar

    longArmed := false

    shortArmed := false

    longArmBar := na

    shortArmBar := na

    longSignalMidpoint := na

    shortSignalMidpoint := na


    if strategy.position_size != 0

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


// ============================================================================
// SECTION 57: EMA200
// ============================================================================

plot(
     showEMA200 ? trendEMA : na,
     title="EMA200",
     color=color.blue,
     linewidth=2
)


// ============================================================================
// SECTION 58: PULLBACK EMA
// ============================================================================

plot(
     showPullbackEMA ? pullbackEMA : na,
     title="Pullback EMA",
     color=color.orange,
     linewidth=1
)


// ============================================================================
// SECTION 59: SAR
// ============================================================================

plot(
     showSAR ? sarValue : na,
     title="SAR",
     color=sarBelow ? color.green : color.red,
     linewidth=1,
     style=plot.style_circles
)


// ============================================================================
// SECTION 60: 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 61: ARM MARKERS
// ============================================================================

plotshape(
     showArmMarkers and sourceLongArm,
     title="Long Arm",
     style=shape.circle,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="A"
)


plotshape(
     showArmMarkers and sourceShortArm,
     title="Short Arm",
     style=shape.circle,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="A"
)


// ============================================================================
// SECTION 62: ENTRY MARKERS
// ============================================================================

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


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


// ============================================================================
// SECTION 63: COUNTERS
// ============================================================================

var int sourceLongArmCount = 0

var int sourceShortArmCount = 0

var int rawLongSetupCount = 0

var int rawShortSetupCount = 0

var int longEntryCount = 0

var int shortEntryCount = 0


if sourceLongArm

    sourceLongArmCount += 1


if sourceShortArm

    sourceShortArmCount += 1


if rawLongSetup

    rawLongSetupCount += 1


if rawShortSetup

    rawShortSetupCount += 1


if finalLongSignal

    longEntryCount += 1


if finalShortSignal

    shortEntryCount += 1


// ============================================================================
// SECTION 64: DIAGNOSTICS
// ============================================================================

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


if showDiagnostics and barstate.islast

    string armText = "NONE"


    if longArmed
        armText := "LONG"

    else if shortArmed
        armText := "SHORT"


    string positionText = "FLAT"


    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


    string trendText = "NEUTRAL"


    if trendUp
        trendText := "UP"

    else if trendDown
        trendText := "DOWN"


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


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


    table.cell(diagnostics, 0, 2, "MACD Bull")
    table.cell(diagnostics, 1, 2, macdUp ? "YES" : "NO")


    table.cell(diagnostics, 0, 3, "SAR Bull")
    table.cell(diagnostics, 1, 3, sarBelow ? "YES" : "NO")


    table.cell(diagnostics, 0, 4, "Armed")
    table.cell(diagnostics, 1, 4, armText)


    table.cell(diagnostics, 0, 5, "Pullback Ref")
    table.cell(diagnostics, 1, 5, pullbackReference)


    table.cell(diagnostics, 0, 6, "Body Ratio")
    table.cell(diagnostics, 1, 6, str.tostring(bodyRatio, "0.00"))


    table.cell(diagnostics, 0, 7, "EMA Slope L")
    table.cell(diagnostics, 1, 7, longSlopeOK ? "YES" : "NO")


    table.cell(diagnostics, 0, 8, "EMA Slope S")
    table.cell(diagnostics, 1, 8, shortSlopeOK ? "YES" : "NO")


    table.cell(diagnostics, 0, 9, "ADX")
    table.cell(diagnostics, 1, 9, str.tostring(adxValue, "0.0"))


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


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


    table.cell(diagnostics, 0, 12, "Arms L / S")
    table.cell(
         diagnostics,
         1,
         12,
         str.tostring(sourceLongArmCount) +
         " / " +
         str.tostring(sourceShortArmCount)
    )


    table.cell(diagnostics, 0, 13, "Setups L / S")
    table.cell(
         diagnostics,
         1,
         13,
         str.tostring(rawLongSetupCount) +
         " / " +
         str.tostring(rawShortSetupCount)
    )


    table.cell(diagnostics, 0, 14, "Entries L / S")
    table.cell(
         diagnostics,
         1,
         14,
         str.tostring(longEntryCount) +
         " / " +
         str.tostring(shortEntryCount)
    )


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


    table.cell(diagnostics, 0, 16, "Target R")
    table.cell(diagnostics, 1, 16, str.tostring(rewardRisk, "0.00"))


    table.cell(diagnostics, 0, 17, "Trigger Mode")
    table.cell(diagnostics, 1, 17, sourceTriggerMode)


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


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