//@version=6

strategy(
     title="CNPS49 DAILY OPEN GRID V2 ADAPTIVE REGIME",
     shorttitle="CNPS49 GRID 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_lines_count=300,
     max_labels_count=300,
     max_bars_back=5000
)


// ============================================================================
// CNPS49 V2 ADAPTIVE GRID REGIME
//
// DAILY OPEN
//      ↓
// ROUND TO GRID
//      ↓
// DEFINE FIXED INTRADAY PRICE MAP
//
//
// RANGE REGIME:
//
// Price is below Daily Open Center
// + sweeps a lower grid
// + closes back above
// = LONG toward next grid inward
//
// Price is above Daily Open Center
// + sweeps an upper grid
// + closes back below
// = SHORT toward next grid inward
//
//
// TREND REGIME:
//
// EMA20 / EMA50 + slope + ADX
//      ↓
// break grid in trend direction
//      ↓
// ARM
//      ↓
// retest grid
//      ↓
// hold grid
//      ↓
// ENTRY toward next grid
//
//
// V2:
//
// - No blind rejection during strong trends
// - No immediate breakout chase
// - Structural stop
// - Target next grid
// - Minimum RR filter
// - Same direction / same grid only once per day
//
// ============================================================================


// ============================================================================
// 1. GRID
// ============================================================================

basisOffset = input.float(
     0.0,
     title="Basis Offset",
     step=0.5,
     group="Grid"
)

gridStep = input.float(
     10.0,
     title="Grid Step",
     minval=1.0,
     step=0.5,
     group="Grid"
)

linesCount = input.int(
     15,
     title="Grid Lines Above / Below",
     minval=1,
     maxval=40,
     group="Grid"
)


// ============================================================================
// 2. REGIME
// ============================================================================

fastEMALength = input.int(
     20,
     title="Fast EMA",
     minval=2,
     group="Regime"
)

slowEMALength = input.int(
     50,
     title="Slow EMA",
     minval=3,
     group="Regime"
)

emaSlopeLookback = input.int(
     3,
     title="EMA Slope Lookback",
     minval=1,
     maxval=30,
     group="Regime"
)

adxLength = input.int(
     14,
     title="ADX Length",
     minval=2,
     group="Regime"
)

adxSmoothing = input.int(
     14,
     title="ADX Smoothing",
     minval=2,
     group="Regime"
)

trendADXThreshold = input.float(
     16.0,
     title="Trend ADX Threshold",
     minval=0.0,
     maxval=100.0,
     step=1.0,
     group="Regime"
)

minimumEMASeparationATR = input.float(
     0.08,
     title="Minimum EMA Separation x ATR",
     minval=0.0,
     maxval=2.0,
     step=0.01,
     group="Regime"
)


// ============================================================================
// 3. REJECTION ENGINE
// ============================================================================

useRangeRejection = input.bool(
     true,
     title="Use Range Rejection?",
     group="Range Rejection"
)

minimumPenetrationATR = input.float(
     0.05,
     title="Minimum Penetration x ATR",
     minval=0.0,
     step=0.05,
     group="Range Rejection"
)

maximumPenetrationATR = input.float(
     0.60,
     title="Maximum Penetration x ATR",
     minval=0.1,
     step=0.05,
     group="Range Rejection"
)

minimumRejectionBodyRatio = input.float(
     0.20,
     title="Minimum Body / Range",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Range Rejection"
)

minimumCloseLocation = input.float(
     0.55,
     title="Minimum Close Location",
     minval=0.50,
     maxval=1.0,
     step=0.05,
     group="Range Rejection"
)


// ============================================================================
// 4. BREAKOUT RETEST
// ============================================================================

useTrendBreakout = input.bool(
     true,
     title="Use Trend Breakout Retest?",
     group="Breakout Retest"
)

breakoutMinimumBodyRatio = input.float(
     0.30,
     title="Breakout Minimum Body / Range",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Breakout Retest"
)

retestWindowBars = input.int(
     5,
     title="Retest Window Bars",
     minval=1,
     maxval=20,
     group="Breakout Retest"
)

retestToleranceATR = input.float(
     0.15,
     title="Retest Tolerance x ATR",
     minval=0.0,
     maxval=2.0,
     step=0.05,
     group="Breakout Retest"
)

retestMinimumBodyRatio = input.float(
     0.15,
     title="Retest Candle Minimum Body / Range",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Breakout Retest"
)


// ============================================================================
// 5. RISK
// ============================================================================

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

stopBufferATR = input.float(
     0.15,
     title="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(
     7.0,
     title="Maximum Risk Points",
     minval=1.0,
     step=0.5,
     group="Risk Management"
)

minimumRR = input.float(
     1.25,
     title="Minimum Reward / Risk",
     minval=0.5,
     maxval=5.0,
     step=0.05,
     group="Risk Management"
)


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

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


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

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

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

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

oneTradePerGridDirection = input.bool(
     true,
     title="One Trade Per Grid / Direction / Day?",
     group="Trade Control"
)


// ============================================================================
// 8. SESSION
// ============================================================================

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

morningSession = input.session(
     "0900-1130",
     title="Morning Entry Session",
     group="Session"
)

afternoonSession = input.session(
     "1300-1430",
     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"
)


// ============================================================================
// 9. DISPLAY
// ============================================================================

showGrid = input.bool(
     true,
     title="Show Grid?",
     group="Display"
)

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

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

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

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


// ============================================================================
// 10. ALERTS
// ============================================================================

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


// ============================================================================
// 11. DAILY OPEN CENTER
// ============================================================================

float dailyOpen = request.security(
     syminfo.tickerid,
     "D",
     open,
     gaps=barmerge.gaps_off,
     lookahead=barmerge.lookahead_on
)

float rawReference = dailyOpen + basisOffset

float centerStrike = math.round(rawReference / gridStep) * gridStep

float centerGrid = centerStrike - basisOffset


// ============================================================================
// 12. INDICATORS
// ============================================================================

float atrValue = ta.atr(atrLength)

float fastEMA = ta.ema(
     close,
     fastEMALength
)

float slowEMA = ta.ema(
     close,
     slowEMALength
)

[plusDI, minusDI, adxValue] = ta.dmi(
     adxLength,
     adxSmoothing
)

float emaSeparationATR = math.abs(fastEMA - slowEMA) / math.max(atrValue, syminfo.mintick)


// ============================================================================
// 13. TREND REGIME
// ============================================================================

bool bullTrend = (
     fastEMA > slowEMA and
     fastEMA > fastEMA[emaSlopeLookback] and
     adxValue >= trendADXThreshold and
     emaSeparationATR >= minimumEMASeparationATR and
     close > centerGrid
)

bool bearTrend = (
     fastEMA < slowEMA and
     fastEMA < fastEMA[emaSlopeLookback] and
     adxValue >= trendADXThreshold and
     emaSeparationATR >= minimumEMASeparationATR and
     close < centerGrid
)

bool trendRegime = (
     bullTrend or
     bearTrend
)

bool rangeRegime = (
     not trendRegime
)


// ============================================================================
// 14. CANDLE METRICS
// ============================================================================

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

float candleBody = math.abs(
     close - open
)

float bodyRatio = candleBody / candleRange

float closeLocation = (close - low) / candleRange


// ============================================================================
// 15. NEAREST GRID LEVELS
// ============================================================================

float relativeClose = (
     close -
     centerGrid
) / gridStep

float lowerGrid = (
     centerGrid +
     math.floor(relativeClose) *
     gridStep
)

float upperGrid = (
     centerGrid +
     math.ceil(relativeClose) *
     gridStep
)


// ============================================================================
// 16. RANGE REJECTION LONG
// ============================================================================

float longPenetration = (
     lowerGrid -
     low
)

bool longRejectionLocationOK = (
     lowerGrid <
     centerGrid
)

bool longPenetrationOK = (
     longPenetration >= atrValue * minimumPenetrationATR and
     longPenetration <= atrValue * maximumPenetrationATR
)

bool rawRejectionLong = (
     barstate.isconfirmed and
     useRangeRejection and
     rangeRegime and
     longRejectionLocationOK and
     low < lowerGrid and
     close > lowerGrid and
     close > open and
     bodyRatio >= minimumRejectionBodyRatio and
     closeLocation >= minimumCloseLocation and
     longPenetrationOK
)


// ============================================================================
// 17. RANGE REJECTION SHORT
// ============================================================================

float shortPenetration = (
     high -
     upperGrid
)

bool shortRejectionLocationOK = (
     upperGrid >
     centerGrid
)

bool shortPenetrationOK = (
     shortPenetration >= atrValue * minimumPenetrationATR and
     shortPenetration <= atrValue * maximumPenetrationATR
)

bool rawRejectionShort = (
     barstate.isconfirmed and
     useRangeRejection and
     rangeRegime and
     shortRejectionLocationOK and
     high > upperGrid and
     close < upperGrid and
     close < open and
     bodyRatio >= minimumRejectionBodyRatio and
     closeLocation <= 1.0 - minimumCloseLocation and
     shortPenetrationOK
)


// ============================================================================
// 18. BREAKOUT GRID LEVELS
// ============================================================================

float previousRelativeClose = (
     close[1] -
     centerGrid
) / gridStep

float breakoutUpperGrid = (
     centerGrid +
     math.ceil(previousRelativeClose) *
     gridStep
)

float breakoutLowerGrid = (
     centerGrid +
     math.floor(previousRelativeClose) *
     gridStep
)


// ============================================================================
// 19. BREAKOUT EVENTS
// ============================================================================

bool longBreakoutEvent = (
     barstate.isconfirmed and
     useTrendBreakout and
     bullTrend and
     close[1] <= breakoutUpperGrid and
     close > breakoutUpperGrid and
     close > open and
     bodyRatio >= breakoutMinimumBodyRatio
)

bool shortBreakoutEvent = (
     barstate.isconfirmed and
     useTrendBreakout and
     bearTrend and
     close[1] >= breakoutLowerGrid and
     close < breakoutLowerGrid and
     close < open and
     bodyRatio >= breakoutMinimumBodyRatio
)


// ============================================================================
// 20. BREAKOUT SETUP STATE
// ============================================================================

var int longBreakBar = na
var float longBreakLevel = na

var int shortBreakBar = na
var float shortBreakLevel = na


if longBreakoutEvent

    longBreakBar := bar_index

    longBreakLevel := breakoutUpperGrid

    shortBreakBar := na

    shortBreakLevel := na


if shortBreakoutEvent

    shortBreakBar := bar_index

    shortBreakLevel := breakoutLowerGrid

    longBreakBar := na

    longBreakLevel := na


// ============================================================================
// 21. BREAKOUT SETUP ACTIVE
// ============================================================================

bool longBreakActive = (
     not na(longBreakBar) and
     bar_index > longBreakBar and
     bar_index - longBreakBar <= retestWindowBars and
     bullTrend
)

bool shortBreakActive = (
     not na(shortBreakBar) and
     bar_index > shortBreakBar and
     bar_index - shortBreakBar <= retestWindowBars and
     bearTrend
)

float retestTolerance = (
     atrValue *
     retestToleranceATR
)


// ============================================================================
// 22. BREAKOUT RETEST LONG
// ============================================================================

bool rawBreakoutLong = (
     barstate.isconfirmed and
     longBreakActive and
     low <= longBreakLevel + retestTolerance and
     close > longBreakLevel and
     close > open and
     bodyRatio >= retestMinimumBodyRatio
)


// ============================================================================
// 23. BREAKOUT RETEST SHORT
// ============================================================================

bool rawBreakoutShort = (
     barstate.isconfirmed and
     shortBreakActive and
     high >= shortBreakLevel - retestTolerance and
     close < shortBreakLevel and
     close < open and
     bodyRatio >= retestMinimumBodyRatio
)


// ============================================================================
// 24. EXPIRE BREAKOUT SETUP
// ============================================================================

if (
     not na(longBreakBar) and
     (
         bar_index - longBreakBar > retestWindowBars or
         close < longBreakLevel - retestTolerance or
         not bullTrend
     )
)

    longBreakBar := na
    longBreakLevel := na


if (
     not na(shortBreakBar) and
     (
         bar_index - shortBreakBar > retestWindowBars or
         close > shortBreakLevel + retestTolerance or
         not bearTrend
     )
)

    shortBreakBar := na
    shortBreakLevel := na


// ============================================================================
// 25. SIGNAL LEVEL
// ============================================================================

bool rawLongSignal = (
     rawRejectionLong or
     rawBreakoutLong
)

bool rawShortSignal = (
     (
         rawRejectionShort or
         rawBreakoutShort
     ) and
     not rawLongSignal
)

bool signalIsRejection = (
     rawRejectionLong or
     rawRejectionShort
)

float longSignalGrid = na
float shortSignalGrid = na


if rawRejectionLong
    longSignalGrid := lowerGrid

else if rawBreakoutLong
    longSignalGrid := longBreakLevel


if rawRejectionShort
    shortSignalGrid := upperGrid

else if rawBreakoutShort
    shortSignalGrid := shortBreakLevel


// ============================================================================
// 26. STOP
// ============================================================================

float candidateLongStop = na
float candidateShortStop = na


if rawLongSignal

    candidateLongStop := low - atrValue * stopBufferATR


if rawShortSignal

    candidateShortStop := high + atrValue * stopBufferATR


// ============================================================================
// 27. TARGET
// ============================================================================
//
// Rejection:
// move one grid back toward Daily Open.
//
// Breakout:
// move one grid further with the trend.
//
// ============================================================================

float candidateLongTarget = na
float candidateShortTarget = na


if rawRejectionLong

    candidateLongTarget := math.min(
         lowerGrid + gridStep,
         centerGrid
    )


else if rawBreakoutLong

    candidateLongTarget := (
         longBreakLevel +
         gridStep
    )


if rawRejectionShort

    candidateShortTarget := math.max(
         upperGrid - gridStep,
         centerGrid
    )


else if rawBreakoutShort

    candidateShortTarget := (
         shortBreakLevel -
         gridStep
    )


// ============================================================================
// 28. RISK / REWARD
// ============================================================================

float longRisk = na
float shortRisk = na

float longReward = na
float shortReward = na

float longRR = na
float shortRR = na


if rawLongSignal

    longRisk := close - candidateLongStop

    longReward := candidateLongTarget - close


    if longRisk > 0

        longRR := longReward / longRisk


if rawShortSignal

    shortRisk := candidateShortStop - close

    shortReward := close - candidateShortTarget


    if shortRisk > 0

        shortRR := shortReward / shortRisk


bool longRiskOK = (
     not na(longRisk) and
     longRisk >= minimumRiskPoints and
     longRisk <= maximumRiskPoints and
     not na(longRR) and
     longRR >= minimumRR
)

bool shortRiskOK = (
     not na(shortRisk) and
     shortRisk >= minimumRiskPoints and
     shortRisk <= maximumRiskPoints and
     not na(shortRR) and
     shortRR >= minimumRR
)


// ============================================================================
// 29. DAILY LEVEL FRESHNESS
// ============================================================================

var float lastLongGrid = na
var float lastShortGrid = na


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

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


if newTradingDay

    lastLongGrid := na

    lastShortGrid := na


bool longGridFresh = (
     not oneTradePerGridDirection or
     na(lastLongGrid) or
     na(longSignalGrid) or
     math.abs(longSignalGrid - lastLongGrid) >= gridStep * 0.50
)

bool shortGridFresh = (
     not oneTradePerGridDirection or
     na(lastShortGrid) or
     na(shortSignalGrid) or
     math.abs(shortSignalGrid - lastShortGrid) >= gridStep * 0.50
)


// ============================================================================
// 30. SESSION
// ============================================================================

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

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

bool entrySession = (
     inMorning or
     inAfternoon
)


// ============================================================================
// 31. FORCE CLOSE TIME
// ============================================================================

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
)


// ============================================================================
// 32. POSITION
// ============================================================================

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
)


// ============================================================================
// 33. DAILY TRADE COUNTER
// ============================================================================

var int tradesToday = 0


if newTradingDay

    tradesToday := 0


// ============================================================================
// 34. COOLDOWN
// ============================================================================

var int lastExitBar = na


if positionJustClosed

    lastExitBar := bar_index


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


// ============================================================================
// 35. ENTRY APPROVAL
// ============================================================================

bool tradeLimitOK = (
     tradesToday <
     maximumTradesPerDay
)

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

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

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


// ============================================================================
// 36. PENDING TRADE STATE
// ============================================================================

var float pendingStop = na
var float pendingTarget = na
var string pendingSetup = ""



// ============================================================================
// 37. LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingStop := candidateLongStop

    pendingTarget := candidateLongTarget

    pendingSetup := (
         signalIsRejection
         ? "RANGE REJECTION"
         : "TREND BREAKOUT"
    )

    lastLongGrid := longSignalGrid

    strategy.entry(
         "Long",
         strategy.long,
         qty=positionContracts,
         comment=signalIsRejection ? "Range Grid Long" : "Trend Grid Long",
         alert_message=signalIsRejection ? "ENTRY_LONG_RANGE_CNPS49_V2" : "ENTRY_LONG_BREAKOUT_CNPS49_V2"
    )

    tradesToday += 1

    longBreakBar := na
    longBreakLevel := na


// ============================================================================
// 38. SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingStop := candidateShortStop

    pendingTarget := candidateShortTarget

    pendingSetup := (
         signalIsRejection
         ? "RANGE REJECTION"
         : "TREND BREAKOUT"
    )

    lastShortGrid := shortSignalGrid

    strategy.entry(
         "Short",
         strategy.short,
         qty=positionContracts,
         comment=signalIsRejection ? "Range Grid Short" : "Trend Grid Short",
         alert_message=signalIsRejection ? "ENTRY_SHORT_RANGE_CNPS49_V2" : "ENTRY_SHORT_BREAKOUT_CNPS49_V2"
    )

    tradesToday += 1

    shortBreakBar := na
    shortBreakLevel := na


// ============================================================================
// 39. ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na
var float activeInitialRisk = na

var float activeStopPrice = na
var float activeTargetPrice = na

var float maximumProfitR = 0.0

var bool breakEvenActivated = false

var string activeSetup = ""



// ============================================================================
// 40. INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeStopPrice := pendingStop

    activeTargetPrice := pendingTarget

    activeInitialRisk := activeEntryPrice - activeStopPrice

    activeSetup := pendingSetup

    maximumProfitR := 0.0

    breakEvenActivated := false

    pendingStop := na
    pendingTarget := na
    pendingSetup := ""



// ============================================================================
// 41. INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeStopPrice := pendingStop

    activeTargetPrice := pendingTarget

    activeInitialRisk := activeStopPrice - activeEntryPrice

    activeSetup := pendingSetup

    maximumProfitR := 0.0

    breakEvenActivated := false

    pendingStop := na
    pendingTarget := na
    pendingSetup := ""



// ============================================================================
// 42. PROFIT R
// ============================================================================

float longProfitR = na
float shortProfitR = na


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

    longProfitR := (high - activeEntryPrice) / activeInitialRisk


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

    shortProfitR := (activeEntryPrice - low) / activeInitialRisk


if (
     inLong and
     not na(longProfitR)
)

    maximumProfitR := math.max(
         maximumProfitR,
         longProfitR
    )


if (
     inShort and
     not na(shortProfitR)
)

    maximumProfitR := math.max(
         maximumProfitR,
         shortProfitR
    )


// ============================================================================
// 43. BREAK EVEN
// ============================================================================

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

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

    breakEvenActivated := true


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

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

    breakEvenActivated := true


// ============================================================================
// 44. EXITS
// ============================================================================

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_CNPS49_V2",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS49_V2" : "EXIT_LONG_SL_CNPS49_V2",
         disable_alert=disableExitAlerts
    )


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_CNPS49_V2",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS49_V2" : "EXIT_SHORT_SL_CNPS49_V2",
         disable_alert=disableExitAlerts
    )


// ============================================================================
// 45. FORCE CLOSE
// ============================================================================

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_CNPS49_V2"
    )


// ============================================================================
// 46. CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeInitialRisk := na

    activeStopPrice := na

    activeTargetPrice := na

    maximumProfitR := 0.0

    breakEvenActivated := false

    activeSetup := ""



// ============================================================================
// 47. GRID DRAWING
// ============================================================================

var array<line> gridLines = array.new_line()


if barstate.islast

    if array.size(gridLines) > 0

        for i = 0 to array.size(gridLines) - 1

            line.delete(
                 array.get(
                     gridLines,
                     i
                 )
            )

        array.clear(gridLines)


    if showGrid

        for i = -linesCount to linesCount

            float level = (
                 centerGrid +
                 i *
                 gridStep
            )

            color levelColor = color.new(
                 color.gray,
                 55
            )

            int levelWidth = 1


            if i == 0

                levelColor := color.orange

                levelWidth := 2


            line newLine = line.new(
                 x1=bar_index - 100,
                 y1=level,
                 x2=bar_index + 20,
                 y2=level,
                 xloc=xloc.bar_index,
                 extend=extend.both,
                 color=levelColor,
                 width=levelWidth
            )


            array.push(
                 gridLines,
                 newLine
            )


// ============================================================================
// 48. EMA DISPLAY
// ============================================================================

plot(
     showEMA ? fastEMA : na,
     title="EMA Fast",
     color=color.blue,
     linewidth=1
)

plot(
     showEMA ? slowEMA : na,
     title="EMA Slow",
     color=color.orange,
     linewidth=2
)


// ============================================================================
// 49. SIGNAL MARKERS
// ============================================================================

plotshape(
     showSignals and
     rawRejectionLong,
     title="Range Long",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="R"
)

plotshape(
     showSignals and
     rawRejectionShort,
     title="Range Short",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="R"
)

plotshape(
     showSignals and
     rawBreakoutLong,
     title="Trend Break Long",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.aqua,
     size=size.small,
     text="B"
)

plotshape(
     showSignals and
     rawBreakoutShort,
     title="Trend Break Short",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.orange,
     size=size.small,
     text="B"
)


// ============================================================================
// 50. ACTIVE SL / TP
// ============================================================================

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
)


// ============================================================================
// 51. DATA WINDOW
// ============================================================================

plot(
     dailyOpen,
     title="Daily Open",
     display=display.data_window
)

plot(
     centerGrid,
     title="Center Grid",
     display=display.data_window
)

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

plot(
     emaSeparationATR,
     title="EMA Separation ATR",
     display=display.data_window
)

plot(
     longRR,
     title="Long Candidate RR",
     display=display.data_window
)

plot(
     shortRR,
     title="Short Candidate RR",
     display=display.data_window
)

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


// ============================================================================
// 52. DIAGNOSTICS
// ============================================================================

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


if (
     showDiagnostics and
     barstate.islast
)

    string regimeText = "RANGE"


    if bullTrend
        regimeText := "BULL TREND"

    else if bearTrend
        regimeText := "BEAR TREND"


    string positionText = "FLAT"


    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


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

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

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

    table.cell(diagnostics, 0, 3, "EMA Sep ATR")
    table.cell(
         diagnostics,
         1,
         3,
         str.tostring(
             emaSeparationATR,
             "0.00"
         )
    )

    table.cell(diagnostics, 0, 4, "Center")
    table.cell(
         diagnostics,
         1,
         4,
         str.tostring(
             centerGrid,
             format.mintick
         )
    )

    table.cell(diagnostics, 0, 5, "Lower Grid")
    table.cell(
         diagnostics,
         1,
         5,
         str.tostring(
             lowerGrid,
             format.mintick
         )
    )

    table.cell(diagnostics, 0, 6, "Upper Grid")
    table.cell(
         diagnostics,
         1,
         6,
         str.tostring(
             upperGrid,
             format.mintick
         )
    )

    table.cell(diagnostics, 0, 7, "Range Long")
    table.cell(
         diagnostics,
         1,
         7,
         rawRejectionLong ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 8, "Range Short")
    table.cell(
         diagnostics,
         1,
         8,
         rawRejectionShort ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 9, "Break Long")
    table.cell(
         diagnostics,
         1,
         9,
         rawBreakoutLong ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 10, "Break Short")
    table.cell(
         diagnostics,
         1,
         10,
         rawBreakoutShort ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 11, "Long RR")
    table.cell(
         diagnostics,
         1,
         11,
         str.tostring(
             longRR,
             "0.00"
         )
    )

    table.cell(diagnostics, 0, 12, "Short RR")
    table.cell(
         diagnostics,
         1,
         12,
         str.tostring(
             shortRR,
             "0.00"
         )
    )

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

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

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

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