//@version=6

strategy(
     title="CNPS50 TREND ANALYSIS V2 QUALITY RE-ENTRY",
     shorttitle="CNPS50 TAS V2",
     overlay=true,
     initial_capital=100000000,
     default_qty_type=strategy.fixed,
     default_qty_value=3,
     pyramiding=0,
     margin_long=0,
     margin_short=0,
     commission_type=strategy.commission.cash_per_contract,
     commission_value=10000,
     slippage=3,
     fill_orders_on_standard_ohlc=true,
     process_orders_on_close=false,
     calc_on_order_fills=true,
     calc_on_every_tick=false,
     max_lines_count=300,
     max_labels_count=300,
     max_bars_back=5000
)


// ============================================================================
// CNPS50 V2 QUALITY FLIP RE-ENTRY
//
// SOURCE DNA RETAINED:
//
// - ATR ratcheting Trend Tracker
// - Regression + Hull Equilibrium
// - Six-component Trend Score
// - RSI / DMI trend information
//
// V2 ENTRY ARCHITECTURE:
//
// TRACKER FLIP
//      ↓
// ARM TREND REGIME
//      ↓
// ┌──────────────────────────────┐
// │ A. STRONG FLIP              │
// │ Score + ADX + Equilibrium   │
// │ + directional candle        │
// └──────────────────────────────┘
//              OR
// ┌──────────────────────────────┐
// │ B. PULLBACK RE-ENTRY        │
// │ Pullback toward equilibrium │
// │ + momentum recovery         │
// │ + continuation candle       │
// └──────────────────────────────┘
//              ↓
//            ENTRY
//
// IMPORTANT:
//
// No immediate opposite reversal by default.
//
// Signal is generated on candle close.
// process_orders_on_close = false
// Entry fills on the next available bar.
//
// Stop is structural.
// Target is calculated from ACTUAL fill price.
//
// ============================================================================


// ============================================================================
// 1. TREND TRACKER
// ============================================================================

trackerATRLength = input.int(
     14,
     title="Tracker ATR Length",
     minval=1,
     group="Trend Tracker"
)

trackerMultiplier = input.float(
     2.2,
     title="Tracker Multiplier",
     minval=0.5,
     step=0.1,
     group="Trend Tracker"
)


// ============================================================================
// 2. EQUILIBRIUM
// ============================================================================

equilibriumLength = input.int(
     55,
     title="Regression Length",
     minval=10,
     group="Equilibrium"
)

equilibriumSmooth = input.int(
     9,
     title="Hull Smoothing",
     minval=2,
     maxval=50,
     group="Equilibrium"
)


// ============================================================================
// 3. TREND SCORE
// ============================================================================

useTrackerScore = input.bool(
     true,
     title="Tracker Direction",
     group="Trend Score"
)

usePriceEquilibriumScore = input.bool(
     true,
     title="Price vs Equilibrium",
     group="Trend Score"
)

useEquilibriumSlopeScore = input.bool(
     true,
     title="Equilibrium Slope",
     group="Trend Score"
)

useRSI50Score = input.bool(
     true,
     title="RSI vs 50",
     group="Trend Score"
)

useRSIAverageScore = input.bool(
     true,
     title="RSI vs RSI Average",
     group="Trend Score"
)

useDMIScore = input.bool(
     true,
     title="+DI vs -DI",
     group="Trend Score"
)

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

rsiAverageLength = input.int(
     14,
     title="RSI Average Length",
     minval=1,
     group="Trend Score"
)

dmiLength = input.int(
     14,
     title="DMI / ADX Length",
     minval=1,
     group="Trend Score"
)


// ============================================================================
// 4. ENTRY ENGINE
// ============================================================================

entryMode = input.string(
     "Both",
     title="Entry Mode",
     options=[
         "Strong Flip",
         "Pullback Re-Entry",
         "Both"
     ],
     group="Entry Engine"
)

bool useStrongFlip = (
     entryMode == "Strong Flip" or
     entryMode == "Both"
)

bool usePullbackEntry = (
     entryMode == "Pullback Re-Entry" or
     entryMode == "Both"
)

setupLifetimeBars = input.int(
     6,
     title="Setup Lifetime Bars",
     minval=1,
     maxval=30,
     group="Entry Engine"
)


// ============================================================================
// 5. STRONG FLIP
// ============================================================================

strongFlipScore = input.float(
     67.0,
     title="Strong Flip Minimum Score %",
     minval=0.0,
     maxval=100.0,
     step=1.0,
     group="Strong Flip"
)

strongFlipADX = input.float(
     18.0,
     title="Strong Flip Minimum ADX",
     minval=0.0,
     maxval=100.0,
     step=1.0,
     group="Strong Flip"
)

strongFlipBodyRatio = input.float(
     0.25,
     title="Minimum Candle Body / Range",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Strong Flip"
)

maximumExtensionATR = input.float(
     1.50,
     title="Maximum Distance From Equilibrium x ATR",
     minval=0.25,
     maxval=10.0,
     step=0.25,
     group="Strong Flip"
)


// ============================================================================
// 6. PULLBACK RE-ENTRY
// ============================================================================

pullbackToleranceATR = input.float(
     0.35,
     title="Equilibrium Touch Tolerance x ATR",
     minval=0.0,
     maxval=3.0,
     step=0.05,
     group="Pullback Re-Entry"
)

reEntryMinimumScore = input.float(
     50.0,
     title="Minimum Re-Entry Score %",
     minval=0.0,
     maxval=100.0,
     step=1.0,
     group="Pullback Re-Entry"
)

reEntryMinimumADX = input.float(
     15.0,
     title="Minimum Re-Entry ADX",
     minval=0.0,
     maxval=100.0,
     step=1.0,
     group="Pullback Re-Entry"
)

reEntryBodyRatio = input.float(
     0.20,
     title="Minimum Trigger Body / Range",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Pullback Re-Entry"
)

requireRSIMomentum = input.bool(
     true,
     title="Require RSI Momentum Recovery?",
     group="Pullback Re-Entry"
)


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


// ============================================================================
// 8. POSITION SIZE
// ============================================================================

positionContracts = input.int(
     3,
     title="Position Size",
     minval=1,
     maxval=20,
     group="Trade Control"
)


// ============================================================================
// 9. FREQUENCY
// ============================================================================

maximumTradesPerDay = input.int(
     4,
     title="Maximum Trades / Day",
     minval=1,
     maxval=20,
     group="Trade Control"
)

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


// ============================================================================
// 10. RISK
// ============================================================================

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

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

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

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

repairGapRisk = input.bool(
     true,
     title="Repair Risk After Next-Bar Gap?",
     group="Risk Management"
)

takeProfitR = input.float(
     1.8,
     title="Take Profit R",
     minval=0.5,
     maxval=10.0,
     step=0.1,
     group="Risk Management"
)


// ============================================================================
// 11. BREAK EVEN
// ============================================================================

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

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

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


// ============================================================================
// 12. OPTIONAL PROFIT TRAIL
// ============================================================================

useTrackerProfitTrail = input.bool(
     false,
     title="Use Tracker Profit Trail?",
     group="Profit Trail"
)

trackerTrailStartR = input.float(
     1.25,
     title="Start Tracker Trail At R",
     minval=0.25,
     maxval=10.0,
     step=0.25,
     group="Profit Trail"
)


// ============================================================================
// 13. OPPOSITE FLIP EXIT
// ============================================================================

exitOnOppositeTrackerFlip = input.bool(
     true,
     title="Exit On Opposite Tracker Flip?",
     group="Trade Control"
)


// ============================================================================
// 14. SESSION
// ============================================================================

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

morningSession = input.session(
     "0905-1125",
     title="Morning Entry Session",
     group="Session"
)

afternoonSession = input.session(
     "1305-1425",
     title="Afternoon Entry Session",
     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"
)


// ============================================================================
// 15. ALERTS
// ============================================================================

disableExitAlerts = input.bool(
     false,
     title="Disable Exit Alerts?",
     group="Alerts"
)


// ============================================================================
// 16. DISPLAY
// ============================================================================

showTracker = input.bool(
     true,
     title="Show Trend Tracker?",
     group="Display"
)

showEquilibrium = input.bool(
     false,
     title="Show Equilibrium?",
     group="Display"
)

showSetupMarkers = input.bool(
     false,
     title="Show Setup 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"
)


// ============================================================================
// 17. TREND TRACKER FUNCTION
// ============================================================================

f_tracker(simple int length, simple float multiplier) =>

    float trackerATRValue = ta.wma(
         ta.tr(true),
         length
    )

    float upperBand = high + trackerATRValue * multiplier

    float lowerBand = low - trackerATRValue * multiplier

    var float trackerValue = na

    var int trackerDirection = 1


    if na(trackerValue)

        trackerValue := lowerBand


    else if trackerDirection == 1

        trackerValue := math.max(
             trackerValue,
             lowerBand
        )


        if close < trackerValue

            trackerDirection := -1

            trackerValue := upperBand


    else

        trackerValue := math.min(
             trackerValue,
             upperBand
        )


        if close > trackerValue

            trackerDirection := 1

            trackerValue := lowerBand


    [
         trackerValue,
         trackerDirection
    ]


// ============================================================================
// 18. CORE INDICATORS
// ============================================================================

[tracker, trackerDirection] = f_tracker(
     trackerATRLength,
     trackerMultiplier
)

float equilibrium = ta.hma(
     ta.linreg(
         close,
         equilibriumLength,
         0
     ),
     equilibriumSmooth
)

float rsiValue = ta.rsi(
     close,
     rsiLength
)

float rsiAverage = ta.sma(
     rsiValue,
     rsiAverageLength
)

[positiveDI, negativeDI, adxValue] = ta.dmi(
     dmiLength,
     dmiLength
)

float riskATR = ta.atr(
     riskATRLength
)


// ============================================================================
// 19. TREND SCORE
// ============================================================================

int scoreMaximum = (
     (useTrackerScore ? 1 : 0) +
     (usePriceEquilibriumScore ? 1 : 0) +
     (useEquilibriumSlopeScore ? 1 : 0) +
     (useRSI50Score ? 1 : 0) +
     (useRSIAverageScore ? 1 : 0) +
     (useDMIScore ? 1 : 0)
)

scoreMaximum := math.max(
     scoreMaximum,
     1
)


int trendScore = 0


trendScore += (
     useTrackerScore
     ? trackerDirection
     : 0
)


trendScore += (
     usePriceEquilibriumScore
     ? (
         close > equilibrium
         ? 1
         : -1
     )
     : 0
)


trendScore += (
     useEquilibriumSlopeScore
     ? (
         equilibrium > equilibrium[1]
         ? 1
         : -1
     )
     : 0
)


trendScore += (
     useRSI50Score
     ? (
         rsiValue > 50
         ? 1
         : -1
     )
     : 0
)


trendScore += (
     useRSIAverageScore
     ? (
         rsiValue > rsiAverage
         ? 1
         : -1
     )
     : 0
)


trendScore += (
     useDMIScore
     ? (
         positiveDI > negativeDI
         ? 1
         : -1
     )
     : 0
)


float scorePercent = float(trendScore) / float(scoreMaximum) * 100.0


// ============================================================================
// 20. TRACKER FLIPS
// ============================================================================

bool bullFlip = (
     trackerDirection == 1 and
     trackerDirection[1] == -1
)

bool bearFlip = (
     trackerDirection == -1 and
     trackerDirection[1] == 1
)


// ============================================================================
// 21. CANDLE QUALITY
// ============================================================================

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

float candleBody = math.abs(
     close - open
)

float bodyRatio = candleBody / candleRange

bool bullishCandle = (
     close > open
)

bool bearishCandle = (
     close < open
)


// ============================================================================
// 22. EQUILIBRIUM DISTANCE
// ============================================================================

float equilibriumDistanceATR = math.abs(close - equilibrium) / math.max(riskATR, syminfo.mintick)


// ============================================================================
// 23. SESSION
// ============================================================================

bool inMorningSession = not na(
     time(
         timeframe.period,
         morningSession,
         strategyTimezone
     )
)

bool inAfternoonSession = not na(
     time(
         timeframe.period,
         afternoonSession,
         strategyTimezone
     )
)

bool entrySession = (
     inMorningSession or
     inAfternoonSession
)


// ============================================================================
// 24. DAILY STATE
// ============================================================================

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


// ============================================================================
// 25. 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
)


// ============================================================================
// 26. COOLDOWN
// ============================================================================

var int lastExitBar = na


if positionJustClosed

    lastExitBar := bar_index


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


// ============================================================================
// 27. TRADE LIMIT
// ============================================================================

bool tradeLimitOK = (
     tradesToday <
     maximumTradesPerDay
)


// ============================================================================
// 28. SETUP STATE
// ============================================================================

var int longSetupBar = na

var float longSetupLow = na

var bool longPulledBack = false


var int shortSetupBar = na

var float shortSetupHigh = na

var bool shortPulledBack = false


// ============================================================================
// 29. ARM LONG SETUP
// ============================================================================

if bullFlip

    longSetupBar := bar_index

    longSetupLow := low

    longPulledBack := false


    shortSetupBar := na

    shortSetupHigh := na

    shortPulledBack := false


// ============================================================================
// 30. ARM SHORT SETUP
// ============================================================================

if bearFlip

    shortSetupBar := bar_index

    shortSetupHigh := high

    shortPulledBack := false


    longSetupBar := na

    longSetupLow := na

    longPulledBack := false


// ============================================================================
// 31. SETUP ACTIVE
// ============================================================================

bool longSetupActive = (
     not na(longSetupBar) and
     trackerDirection == 1 and
     bar_index - longSetupBar <= setupLifetimeBars
)

bool shortSetupActive = (
     not na(shortSetupBar) and
     trackerDirection == -1 and
     bar_index - shortSetupBar <= setupLifetimeBars
)


// ============================================================================
// 32. UPDATE SETUP EXTREMES
// ============================================================================

if longSetupActive

    longSetupLow := math.min(
         nz(longSetupLow, low),
         low
    )


if shortSetupActive

    shortSetupHigh := math.max(
         nz(shortSetupHigh, high),
         high
    )


// ============================================================================
// 33. PULLBACK DETECTION
// ============================================================================

if longSetupActive

    bool touchedLongPullback = (
         low <= equilibrium + riskATR * pullbackToleranceATR
    )


    if touchedLongPullback

        longPulledBack := true


if shortSetupActive

    bool touchedShortPullback = (
         high >= equilibrium - riskATR * pullbackToleranceATR
    )


    if touchedShortPullback

        shortPulledBack := true


// ============================================================================
// 34. EXPIRE SETUPS
// ============================================================================

if (
     not na(longSetupBar) and
     (
         bar_index - longSetupBar > setupLifetimeBars or
         trackerDirection != 1
     )
)

    longSetupBar := na

    longSetupLow := na

    longPulledBack := false


if (
     not na(shortSetupBar) and
     (
         bar_index - shortSetupBar > setupLifetimeBars or
         trackerDirection != -1
     )
)

    shortSetupBar := na

    shortSetupHigh := na

    shortPulledBack := false


// ============================================================================
// 35. STRONG FLIP LONG
// ============================================================================

bool strongFlipLong = (
     useStrongFlip and
     bullFlip and
     scorePercent >= strongFlipScore and
     adxValue >= strongFlipADX and
     close > equilibrium and
     equilibrium > equilibrium[1] and
     bullishCandle and
     bodyRatio >= strongFlipBodyRatio and
     equilibriumDistanceATR <= maximumExtensionATR
)


// ============================================================================
// 36. STRONG FLIP SHORT
// ============================================================================

bool strongFlipShort = (
     useStrongFlip and
     bearFlip and
     scorePercent <= -strongFlipScore and
     adxValue >= strongFlipADX and
     close < equilibrium and
     equilibrium < equilibrium[1] and
     bearishCandle and
     bodyRatio >= strongFlipBodyRatio and
     equilibriumDistanceATR <= maximumExtensionATR
)


// ============================================================================
// 37. RSI MOMENTUM
// ============================================================================

bool rsiLongMomentumOK = (
     not requireRSIMomentum or
     (
         rsiValue >= 50 and
         rsiValue > rsiValue[1]
     )
)

bool rsiShortMomentumOK = (
     not requireRSIMomentum or
     (
         rsiValue <= 50 and
         rsiValue < rsiValue[1]
     )
)


// ============================================================================
// 38. PULLBACK RE-ENTRY LONG
// ============================================================================

bool reEntryLong = (
     usePullbackEntry and
     longSetupActive and
     bar_index > longSetupBar and
     longPulledBack and
     scorePercent >= reEntryMinimumScore and
     adxValue >= reEntryMinimumADX and
     close > equilibrium and
     equilibrium >= equilibrium[1] and
     bullishCandle and
     bodyRatio >= reEntryBodyRatio and
     rsiLongMomentumOK
)


// ============================================================================
// 39. PULLBACK RE-ENTRY SHORT
// ============================================================================

bool reEntryShort = (
     usePullbackEntry and
     shortSetupActive and
     bar_index > shortSetupBar and
     shortPulledBack and
     scorePercent <= -reEntryMinimumScore and
     adxValue >= reEntryMinimumADX and
     close < equilibrium and
     equilibrium <= equilibrium[1] and
     bearishCandle and
     bodyRatio >= reEntryBodyRatio and
     rsiShortMomentumOK
)


// ============================================================================
// 40. RAW ENTRY
// ============================================================================

bool rawLongSignal = (
     barstate.isconfirmed and
     (
         strongFlipLong or
         reEntryLong
     )
)

bool rawShortSignal = (
     barstate.isconfirmed and
     (
         strongFlipShort or
         reEntryShort
     ) and
     not rawLongSignal
)


// ============================================================================
// 41. STRUCTURAL STOP
// ============================================================================

float candidateLongStop = na

float candidateShortStop = na


if rawLongSignal

    float longStructure = nz(
         longSetupLow,
         low
    )

    candidateLongStop := math.min(
         longStructure,
         tracker
    ) - riskATR * stopBufferATR


if rawShortSignal

    float shortStructure = nz(
         shortSetupHigh,
         high
    )

    candidateShortStop := math.max(
         shortStructure,
         tracker
    ) + riskATR * stopBufferATR


// ============================================================================
// 42. SIGNAL RISK
// ============================================================================

float candidateLongRisk = na

float candidateShortRisk = na


if rawLongSignal and not na(candidateLongStop)

    candidateLongRisk := close - candidateLongStop


if rawShortSignal and not na(candidateShortStop)

    candidateShortRisk := candidateShortStop - close


// ============================================================================
// 43. RISK FILTER
// ============================================================================

bool longRiskOK = (
     not na(candidateLongRisk) and
     candidateLongRisk >= minimumRiskPoints and
     candidateLongRisk <= maximumRiskPoints
)

bool shortRiskOK = (
     not na(candidateShortRisk) and
     candidateShortRisk >= minimumRiskPoints and
     candidateShortRisk <= maximumRiskPoints
)


// ============================================================================
// 44. COMMON ENTRY APPROVAL
// ============================================================================

bool commonEntryOK = (
     flat and
     entrySession and
     cooldownOK and
     tradeLimitOK
)


// ============================================================================
// 45. FINAL LONG
// ============================================================================

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


// ============================================================================
// 46. FINAL SHORT
// ============================================================================

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


// ============================================================================
// 47. PENDING TRADE STATE
// ============================================================================

var float pendingStop = na

var string pendingSetup = ""



// ============================================================================
// 48. LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingStop := candidateLongStop

    pendingSetup := (
         strongFlipLong
         ? "STRONG FLIP"
         : "RE-ENTRY"
    )


    strategy.entry(
         "Long",
         strategy.long,
         qty=positionContracts,
         comment=strongFlipLong ? "Strong Flip Long" : "Re-Entry Long",
         alert_message=strongFlipLong ? "ENTRY_LONG_STRONG_CNPS50_V2" : "ENTRY_LONG_REENTRY_CNPS50_V2"
    )


    // Protective stop is submitted while the entry is still pending.
    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=pendingStop,
         alert_loss="EXIT_LONG_INITIAL_SL_CNPS50_V2",
         disable_alert=disableExitAlerts
    )


    tradesToday += 1


    longSetupBar := na

    longSetupLow := na

    longPulledBack := false


// ============================================================================
// 49. SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingStop := candidateShortStop

    pendingSetup := (
         strongFlipShort
         ? "STRONG FLIP"
         : "RE-ENTRY"
    )


    strategy.entry(
         "Short",
         strategy.short,
         qty=positionContracts,
         comment=strongFlipShort ? "Strong Flip Short" : "Re-Entry Short",
         alert_message=strongFlipShort ? "ENTRY_SHORT_STRONG_CNPS50_V2" : "ENTRY_SHORT_REENTRY_CNPS50_V2"
    )


    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=pendingStop,
         alert_loss="EXIT_SHORT_INITIAL_SL_CNPS50_V2",
         disable_alert=disableExitAlerts
    )


    tradesToday += 1


    shortSetupBar := na

    shortSetupHigh := na

    shortPulledBack := false


// ============================================================================
// 50. ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeInitialRisk = na

var float activeStopPrice = na

var float activeTargetPrice = na

var float maximumCloseProfitR = 0.0

var bool breakEvenActivated = false

var bool trackerTrailActivated = false

var string activeSetup = ""



// ============================================================================
// 51. INITIALIZE LONG FROM ACTUAL FILL
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeStopPrice := pendingStop

    float actualRisk = activeEntryPrice - activeStopPrice


    if repairGapRisk

        if (
             na(actualRisk) or
             actualRisk < minimumRiskPoints or
             actualRisk > maximumRiskPoints
        )

            float repairedRisk = math.min(
                 maximumRiskPoints,
                 math.max(
                     minimumRiskPoints,
                     nz(actualRisk, riskATR)
                 )
            )

            activeStopPrice := activeEntryPrice - repairedRisk

            actualRisk := repairedRisk


    activeInitialRisk := actualRisk

    activeTargetPrice := activeEntryPrice + activeInitialRisk * takeProfitR

    activeSetup := pendingSetup

    maximumCloseProfitR := 0.0

    breakEvenActivated := false

    trackerTrailActivated := false

    pendingStop := na

    pendingSetup := ""



// ============================================================================
// 52. INITIALIZE SHORT FROM ACTUAL FILL
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeStopPrice := pendingStop

    float actualRisk = activeStopPrice - activeEntryPrice


    if repairGapRisk

        if (
             na(actualRisk) or
             actualRisk < minimumRiskPoints or
             actualRisk > maximumRiskPoints
        )

            float repairedRisk = math.min(
                 maximumRiskPoints,
                 math.max(
                     minimumRiskPoints,
                     nz(actualRisk, riskATR)
                 )
            )

            activeStopPrice := activeEntryPrice + repairedRisk

            actualRisk := repairedRisk


    activeInitialRisk := actualRisk

    activeTargetPrice := activeEntryPrice - activeInitialRisk * takeProfitR

    activeSetup := pendingSetup

    maximumCloseProfitR := 0.0

    breakEvenActivated := false

    trackerTrailActivated := false

    pendingStop := na

    pendingSetup := ""



// ============================================================================
// 53. CLOSE-BASED PROFIT R
// ============================================================================
//
// Deliberately uses CLOSE instead of intrabar high / low.
//
// This prevents an intrabar wick from unrealistically activating BE / trailing
// before the strategy has actually observed that condition at candle close.
//
// ============================================================================

float longCloseProfitR = na

float shortCloseProfitR = na


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

    longCloseProfitR := (close - activeEntryPrice) / activeInitialRisk


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

    shortCloseProfitR := (activeEntryPrice - close) / activeInitialRisk


if (
     inLong and
     not na(longCloseProfitR)
)

    maximumCloseProfitR := math.max(
         maximumCloseProfitR,
         longCloseProfitR
    )


if (
     inShort and
     not na(shortCloseProfitR)
)

    maximumCloseProfitR := math.max(
         maximumCloseProfitR,
         shortCloseProfitR
    )


// ============================================================================
// 54. BREAK EVEN LONG
// ============================================================================

if (
     inLong and
     useBreakEven and
     not breakEvenActivated and
     maximumCloseProfitR >= breakEvenTriggerR
)

    activeStopPrice := math.max(
         activeStopPrice,
         activeEntryPrice + breakEvenOffset
    )

    breakEvenActivated := true


// ============================================================================
// 55. BREAK EVEN SHORT
// ============================================================================

if (
     inShort and
     useBreakEven and
     not breakEvenActivated and
     maximumCloseProfitR >= breakEvenTriggerR
)

    activeStopPrice := math.min(
         activeStopPrice,
         activeEntryPrice - breakEvenOffset
    )

    breakEvenActivated := true


// ============================================================================
// 56. OPTIONAL TRACKER PROFIT TRAIL LONG
// ============================================================================

if (
     inLong and
     useTrackerProfitTrail and
     maximumCloseProfitR >= trackerTrailStartR and
     trackerDirection == 1
)

    trackerTrailActivated := true

    activeStopPrice := math.max(
         activeStopPrice,
         tracker
    )


// ============================================================================
// 57. OPTIONAL TRACKER PROFIT TRAIL SHORT
// ============================================================================

if (
     inShort and
     useTrackerProfitTrail and
     maximumCloseProfitR >= trackerTrailStartR and
     trackerDirection == -1
)

    trackerTrailActivated := true

    activeStopPrice := math.min(
         activeStopPrice,
         tracker
    )


// ============================================================================
// 58. ACTIVE LONG EXIT
// ============================================================================

if (
     inLong and
     not na(activeStopPrice) and
     not na(activeTargetPrice)
)

    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=activeStopPrice,
         limit=activeTargetPrice,
         alert_profit="EXIT_LONG_TP_CNPS50_V2",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS50_V2" : "EXIT_LONG_SL_CNPS50_V2",
         disable_alert=disableExitAlerts
    )


// ============================================================================
// 59. ACTIVE SHORT EXIT
// ============================================================================

if (
     inShort and
     not na(activeStopPrice) and
     not na(activeTargetPrice)
)

    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=activeStopPrice,
         limit=activeTargetPrice,
         alert_profit="EXIT_SHORT_TP_CNPS50_V2",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS50_V2" : "EXIT_SHORT_SL_CNPS50_V2",
         disable_alert=disableExitAlerts
    )


// ============================================================================
// 60. OPPOSITE TRACKER FLIP EXIT
// ============================================================================

if (
     inLong and
     exitOnOppositeTrackerFlip and
     bearFlip
)

    strategy.close(
         "Long",
         comment="Tracker Flip",
         alert_message="TRACKER_FLIP_EXIT_LONG_CNPS50_V2"
    )


if (
     inShort and
     exitOnOppositeTrackerFlip and
     bullFlip
)

    strategy.close(
         "Short",
         comment="Tracker Flip",
         alert_message="TRACKER_FLIP_EXIT_SHORT_CNPS50_V2"
    )


// ============================================================================
// 61. FORCE CLOSE
// ============================================================================

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

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

int forceCloseTotal = (
     forceCloseHour * 60 +
     forceCloseMinute
)

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


if (
     forceCloseBar and
     strategy.position_size != 0
)

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


// ============================================================================
// 62. CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeInitialRisk := na

    activeStopPrice := na

    activeTargetPrice := na

    maximumCloseProfitR := 0.0

    breakEvenActivated := false

    trackerTrailActivated := false

    activeSetup := ""



// ============================================================================
// 63. TRACKER DISPLAY
// ============================================================================

plot(
     showTracker and
     trackerDirection == 1
     ? tracker
     : na,
     title="Bull Tracker",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)

plot(
     showTracker and
     trackerDirection == -1
     ? tracker
     : na,
     title="Bear Tracker",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// 64. EQUILIBRIUM DISPLAY
// ============================================================================

plot(
     showEquilibrium
     ? equilibrium
     : na,
     title="Equilibrium",
     color=color.gray,
     linewidth=2
)


// ============================================================================
// 65. SETUP MARKERS
// ============================================================================

plotshape(
     showSetupMarkers and
     bullFlip,
     title="Bull Flip Setup",
     style=shape.circle,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="F"
)

plotshape(
     showSetupMarkers and
     bearFlip,
     title="Bear Flip Setup",
     style=shape.circle,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="F"
)


// ============================================================================
// 66. ENTRY MARKERS
// ============================================================================

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

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


// ============================================================================
// 67. ACTIVE STOP / TARGET
// ============================================================================

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

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


// ============================================================================
// 68. DATA WINDOW
// ============================================================================

plot(
     scorePercent,
     title="Trend Score %",
     display=display.data_window
)

plot(
     adxValue,
     title="ADX",
     display=display.data_window
)

plot(
     rsiValue,
     title="RSI",
     display=display.data_window
)

plot(
     equilibriumDistanceATR,
     title="Distance From Equilibrium ATR",
     display=display.data_window
)

plot(
     activeInitialRisk,
     title="Active Initial Risk",
     display=display.data_window
)

plot(
     maximumCloseProfitR,
     title="Maximum Close Profit R",
     display=display.data_window
)


// ============================================================================
// 69. DIAGNOSTIC COUNTERS
// ============================================================================

var int bullFlipCount = 0

var int bearFlipCount = 0

var int strongLongCount = 0

var int strongShortCount = 0

var int reEntryLongCount = 0

var int reEntryShortCount = 0


if bullFlip
    bullFlipCount += 1


if bearFlip
    bearFlipCount += 1


if strongFlipLong
    strongLongCount += 1


if strongFlipShort
    strongShortCount += 1


if reEntryLong
    reEntryLongCount += 1


if reEntryShort
    reEntryShortCount += 1


// ============================================================================
// 70. DIAGNOSTICS
// ============================================================================

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


if (
     showDiagnostics and
     barstate.islast
)

    string trendText = (
         trackerDirection == 1
         ? "BULL"
         : "BEAR"
    )


    string setupText = "NONE"


    if longSetupActive
        setupText := longPulledBack ? "LONG PULLBACK" : "LONG ARMED"

    else if shortSetupActive
        setupText := shortPulledBack ? "SHORT PULLBACK" : "SHORT ARMED"


    string positionText = "FLAT"


    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


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

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

    table.cell(diagnostics, 0, 2, "Score")
    table.cell(
         diagnostics,
         1,
         2,
         str.tostring(scorePercent, "0") + "%"
    )

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

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

    table.cell(diagnostics, 0, 5, "Eq Distance")
    table.cell(
         diagnostics,
         1,
         5,
         str.tostring(equilibriumDistanceATR, "0.00") + " ATR"
    )

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

    table.cell(diagnostics, 0, 7, "Strong L / S")
    table.cell(
         diagnostics,
         1,
         7,
         str.tostring(strongLongCount) +
         " / " +
         str.tostring(strongShortCount)
    )

    table.cell(diagnostics, 0, 8, "ReEntry L / S")
    table.cell(
         diagnostics,
         1,
         8,
         str.tostring(reEntryLongCount) +
         " / " +
         str.tostring(reEntryShortCount)
    )

    table.cell(diagnostics, 0, 9, "Flip L / S")
    table.cell(
         diagnostics,
         1,
         9,
         str.tostring(bullFlipCount) +
         " / " +
         str.tostring(bearFlipCount)
    )

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

    table.cell(diagnostics, 0, 11, "Cooldown")
    table.cell(
         diagnostics,
         1,
         11,
         cooldownOK ? "READY" : "WAIT"
    )

    table.cell(diagnostics, 0, 12, "Entry Session")
    table.cell(
         diagnostics,
         1,
         12,
         entrySession ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 13, "Active Setup")
    table.cell(
         diagnostics,
         1,
         13,
         activeSetup == "" ? "-" : activeSetup
    )

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

    table.cell(diagnostics, 0, 15, "Max Close R")
    table.cell(
         diagnostics,
         1,
         15,
         str.tostring(maximumCloseProfitR, "0.00")
    )

    table.cell(diagnostics, 0, 16, "Active Stop")
    table.cell(
         diagnostics,
         1,
         16,
         str.tostring(activeStopPrice, format.mintick)
    )

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