//@version=6

strategy(
     title="CNPS44 OPEN LEGACY V2 QUALITY",
     shorttitle="CNPS44 LEGACY V2",
     overlay=true,
     initial_capital=100000000,
     default_qty_type=strategy.fixed,
     default_qty_value=3,
     pyramiding=0,
     margin_long=0,
     margin_short=0,
     slippage=3,
     commission_type=strategy.commission.cash_per_contract,
     commission_value=10000,
     fill_orders_on_standard_ohlc=true,
     process_orders_on_close=true,
     calc_on_order_fills=true,
     max_labels_count=300,
     max_lines_count=300,
     max_bars_back=5000
)


// ============================================================================
// CNPS44 OPEN LEGACY V2 QUALITY
//
// ORIGINAL SIGNAL ENGINES:
//
// 1. FVG Pullback
// 2. EMA Trend
//
// ORIGINAL SENSITIVITY MODES:
//
// Aggressive
// Balanced
// Conservative
//
// V2 QUALITY ADAPTATION FOR VN30:
//
// - EMA50 slope confirmation
// - ADX quality floor
// - Confirmation candle body quality
// - Optional previous High / Low break
// - Maximum trades per day
// - Cooldown after exit
// - No automatic reversal by default
// - Volatility-adjusted risk
// - Minimum / maximum accepted stop distance
// - R-multiple target
//
// ============================================================================


// ============================================================================
// SECTION 1: SIGNAL ENGINE
// ============================================================================

entryMethod = input.string(
     "FVG Pullback",
     title="Entry Method",
     options=[
         "FVG Pullback",
         "EMA Trend"
     ],
     group="Signal Engine"
)


// ============================================================================
// SECTION 2: SOURCE SENSITIVITY
// ============================================================================

sensitivity = input.string(
     "Balanced",
     title="Sensitivity",
     options=[
         "Aggressive",
         "Balanced",
         "Conservative"
     ],
     group="Signal Engine"
)


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


// ============================================================================
// SECTION 4: POSITION SIZE
// ============================================================================

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


// ============================================================================
// SECTION 5: TRADE FREQUENCY
// ============================================================================

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

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


// ============================================================================
// SECTION 6: OPPOSITE SIGNAL BEHAVIOR
// ============================================================================
//
// V1/source-style behavior can reverse immediately.
//
// V2 default:
// Do not automatically reverse.
// This reduces whipsaw during sideways conditions.
//
// ============================================================================

exitOnOppositeSignal = input.bool(
     true,
     title="Exit On Opposite Signal?",
     group="Trade Control"
)

reverseOnOppositeSignal = input.bool(
     false,
     title="Reverse Immediately On Opposite Signal?",
     group="Trade Control"
)


// ============================================================================
// SECTION 7: VN30 QUALITY LAYER
// ============================================================================

useQualityLayer = input.bool(
     true,
     title="Use VN30 Quality Layer?",
     group="Quality Filter"
)

emaSlopeLookback = input.int(
     3,
     title="EMA50 Slope Lookback",
     minval=1,
     maxval=50,
     group="Quality Filter"
)

minimumADX = input.float(
     18.0,
     title="Minimum ADX",
     minval=0.0,
     step=1.0,
     group="Quality Filter"
)

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

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


// ============================================================================
// SECTION 8: RISK MODE
// ============================================================================

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


// ============================================================================
// SECTION 9: ATR RISK
// ============================================================================

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

atrStopMultiplier = input.float(
     1.20,
     title="ATR Stop Multiplier",
     minval=0.10,
     step=0.10,
     group="Risk Management"
)

rewardRisk = input.float(
     1.80,
     title="Take Profit R Multiple",
     minval=0.50,
     step=0.10,
     group="Risk Management"
)

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

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


// ============================================================================
// SECTION 10: FIXED RISK ALTERNATIVE
// ============================================================================

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

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


// ============================================================================
// SECTION 11: 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,
     step=0.25,
     group="Break Even"
)

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


// ============================================================================
// SECTION 12: SESSION
// ============================================================================

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

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

entryStartMinute = input.int(
     5,
     title="Entry 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(
     30,
     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 13: ALERTS
// ============================================================================

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


// ============================================================================
// SECTION 14: DISPLAY
// ============================================================================

showEMA50 = input.bool(
     true,
     title="Show EMA50?",
     group="Display"
)

showFastSlowEMA = input.bool(
     false,
     title="Show Fast / Slow EMA?",
     group="Display"
)

showFVG = input.bool(
     false,
     title="Show Active FVG?",
     group="Display"
)

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

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

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


// ============================================================================
// SECTION 15: SOURCE PRESETS
// ============================================================================

int fvgLifetime = 24
float minimumFVGATR = 0.25
int fastEMALength = 13
int slowEMALength = 34

if sensitivity == "Aggressive"

    fvgLifetime := 12
    minimumFVGATR := 0.10
    fastEMALength := 8
    slowEMALength := 21

else if sensitivity == "Conservative"

    fvgLifetime := 40
    minimumFVGATR := 0.40
    fastEMALength := 21
    slowEMALength := 55


// ============================================================================
// SECTION 16: CORE INDICATORS
// ============================================================================

float atrValue = ta.atr(atrLength)

float ema50 = ta.ema(close, 50)

float fastEMA = ta.ema(
     close,
     fastEMALength
)

float slowEMA = ta.ema(
     close,
     slowEMALength
)

float rsiValue = ta.rsi(
     close,
     14
)

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


// ============================================================================
// SECTION 17: HIGHER TIMEFRAME
// ============================================================================

string higherTimeframe = timeframe.from_seconds(
     timeframe.in_seconds() * 4
)

float higherTimeframeClose = request.security(
     syminfo.tickerid,
     higherTimeframe,
     close[1],
     gaps=barmerge.gaps_off,
     lookahead=barmerge.lookahead_on
)

float higherTimeframeEMA50 = request.security(
     syminfo.tickerid,
     higherTimeframe,
     ta.ema(close, 50)[1],
     gaps=barmerge.gaps_off,
     lookahead=barmerge.lookahead_on
)

bool higherTimeframeBullish = (
     not na(higherTimeframeEMA50) and
     higherTimeframeClose > higherTimeframeEMA50
)

bool higherTimeframeBearish = (
     not na(higherTimeframeEMA50) and
     higherTimeframeClose < higherTimeframeEMA50
)


// ============================================================================
// SECTION 18: FVG STATE
// ============================================================================

var float bullishFVGTop = na
var float bullishFVGBottom = na
var int bullishFVGBar = na

var float bearishFVGTop = na
var float bearishFVGBottom = na
var int bearishFVGBar = na


// ============================================================================
// SECTION 19: ACTIVE FVG
// ============================================================================

bool bullishFVGLive = (
     not na(bullishFVGBar) and
     bar_index - bullishFVGBar > 0 and
     bar_index - bullishFVGBar <= fvgLifetime
)

bool bearishFVGLive = (
     not na(bearishFVGBar) and
     bar_index - bearishFVGBar > 0 and
     bar_index - bearishFVGBar <= fvgLifetime
)


// ============================================================================
// SECTION 20: FVG PULLBACK SOURCE SIGNAL
// ============================================================================

bool fvgBuySignal = (
     bullishFVGLive and
     low <= bullishFVGTop and
     close > open and
     close > bullishFVGBottom
)

bool fvgSellSignal = (
     bearishFVGLive and
     high >= bearishFVGBottom and
     close < open and
     close < bearishFVGTop
)


// ============================================================================
// SECTION 21: CONSUME FVG
// ============================================================================

if fvgBuySignal
    bullishFVGBar := na

if fvgSellSignal
    bearishFVGBar := na


// ============================================================================
// SECTION 22: CREATE BULLISH FVG
// ============================================================================

bool newBullishFVG = (
     bar_index >= 2 and
     low > high[2] and
     low - high[2] >= minimumFVGATR * atrValue
)

if newBullishFVG

    bullishFVGBottom := high[2]
    bullishFVGTop := low
    bullishFVGBar := bar_index


// ============================================================================
// SECTION 23: CREATE BEARISH FVG
// ============================================================================

bool newBearishFVG = (
     bar_index >= 2 and
     high < low[2] and
     low[2] - high >= minimumFVGATR * atrValue
)

if newBearishFVG

    bearishFVGTop := low[2]
    bearishFVGBottom := high
    bearishFVGBar := bar_index


// ============================================================================
// SECTION 24: EMA TREND SOURCE ENGINE
// ============================================================================

bool bullishTrendState = (
     fastEMA > slowEMA and
     close > fastEMA and
     rsiValue > 50
)

bool bearishTrendState = (
     fastEMA < slowEMA and
     close < fastEMA and
     rsiValue < 50
)

bool emaTrendBuySignal = (
     bullishTrendState and
     not bullishTrendState[1]
)

bool emaTrendSellSignal = (
     bearishTrendState and
     not bearishTrendState[1]
)


// ============================================================================
// SECTION 25: SELECT SOURCE ENGINE
// ============================================================================

bool rawBuySignal = false
bool rawSellSignal = false

if entryMethod == "FVG Pullback"

    rawBuySignal := fvgBuySignal
    rawSellSignal := fvgSellSignal

else

    rawBuySignal := emaTrendBuySignal
    rawSellSignal := emaTrendSellSignal


// ============================================================================
// SECTION 26: ORIGINAL SOURCE QUALITY FILTER
// ============================================================================

bool sourceBuyQuality = false
bool sourceSellQuality = false

if sensitivity == "Aggressive"

    sourceBuyQuality := true
    sourceSellQuality := true

else if sensitivity == "Balanced"

    sourceBuyQuality := close > ema50
    sourceSellQuality := close < ema50

else

    sourceBuyQuality := (
         close > ema50 and
         higherTimeframeBullish and
         adxValue >= 20
    )

    sourceSellQuality := (
         close < ema50 and
         higherTimeframeBearish and
         adxValue >= 20
    )


// ============================================================================
// SECTION 27: VN30 EMA50 SLOPE
// ============================================================================

bool ema50SlopeBullish = (
     ema50 > ema50[emaSlopeLookback]
)

bool ema50SlopeBearish = (
     ema50 < ema50[emaSlopeLookback]
)


// ============================================================================
// SECTION 28: CANDLE QUALITY
// ============================================================================

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

float candleBody = math.abs(
     close - open
)

float bodyRatio = candleBody / candleRange

bool bullishConfirmationCandle = (
     close > open and
     bodyRatio >= minimumBodyRatio
)

bool bearishConfirmationCandle = (
     close < open and
     bodyRatio >= minimumBodyRatio
)


// ============================================================================
// SECTION 29: PREVIOUS HIGH / LOW CONFIRMATION
// ============================================================================

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

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


// ============================================================================
// SECTION 30: VN30 QUALITY FILTER
// ============================================================================

bool vn30LongQuality = (
     not useQualityLayer or
     (
         ema50SlopeBullish and
         adxValue >= minimumADX and
         bullishConfirmationCandle and
         bullishBreakConfirmation
     )
)

bool vn30ShortQuality = (
     not useQualityLayer or
     (
         ema50SlopeBearish and
         adxValue >= minimumADX and
         bearishConfirmationCandle and
         bearishBreakConfirmation
     )
)


// ============================================================================
// SECTION 31: FINAL SOURCE SIGNAL
// ============================================================================

bool sourceBuySignal = (
     rawBuySignal and
     sourceBuyQuality and
     vn30LongQuality and
     barstate.isconfirmed
)

bool sourceSellSignal = (
     rawSellSignal and
     sourceSellQuality and
     vn30ShortQuality and
     not sourceBuySignal and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 32: SESSION ENGINE
// ============================================================================

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

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

int entryStartTotal = (
     entryStartHour * 60 +
     entryStartMinute
)

int lastEntryTotal = (
     lastEntryHour * 60 +
     lastEntryMinute
)

int forceCloseTotal = (
     forceCloseHour * 60 +
     forceCloseMinute
)

bool entrySession = (
     currentMinute >= entryStartTotal and
     currentMinute < lastEntryTotal and
     currentCloseMinute < forceCloseTotal
)


// ============================================================================
// SECTION 33: DAILY COUNTER
// ============================================================================

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

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

var int tradesToday = 0

if newTradingDay
    tradesToday := 0


// ============================================================================
// SECTION 34: 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 35: COOLDOWN
// ============================================================================

var int lastExitBar = na

if positionJustClosed
    lastExitBar := bar_index

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


// ============================================================================
// SECTION 36: DAILY LIMIT
// ============================================================================

bool dailyTradeLimitOK = (
     tradesToday < maximumTradesPerDay
)


// ============================================================================
// SECTION 37: CALCULATE SIGNAL RISK
// ============================================================================

float longRiskDistance = na
float shortRiskDistance = na

if riskMode == "Fixed Points"

    longRiskDistance := fixedStopPoints
    shortRiskDistance := fixedStopPoints

else

    float baseATRDistance = atrValue * atrStopMultiplier

    longRiskDistance := math.max(
         baseATRDistance,
         minimumRiskPoints
    )

    shortRiskDistance := math.max(
         baseATRDistance,
         minimumRiskPoints
    )


// ============================================================================
// SECTION 38: RISK VALIDATION
// ============================================================================

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

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


// ============================================================================
// SECTION 39: ENTRY ELIGIBILITY
// ============================================================================

bool commonEntryOK = (
     entrySession and
     dailyTradeLimitOK and
     cooldownOK
)


// ============================================================================
// SECTION 40: FINAL LONG ENTRY
// ============================================================================

bool finalLongEntry = (
     flat and
     allowLong and
     sourceBuySignal and
     commonEntryOK and
     longRiskOK
)


// ============================================================================
// SECTION 41: FINAL SHORT ENTRY
// ============================================================================

bool finalShortEntry = (
     flat and
     allowShort and
     sourceSellSignal and
     commonEntryOK and
     shortRiskOK and
     not finalLongEntry
)


// ============================================================================
// SECTION 42: PENDING RISK
// ============================================================================

var float pendingRiskDistance = na


// ============================================================================
// SECTION 43: LONG ENTRY
// ============================================================================

if finalLongEntry

    pendingRiskDistance := longRiskDistance

    strategy.entry(
         "Long",
         strategy.long,
         qty=positionContracts,
         comment=entryMethod == "FVG Pullback" ? "FVG Long" : "EMA Long",
         alert_message="ENTRY_LONG_CNPS44_V2"
    )

    tradesToday += 1


// ============================================================================
// SECTION 44: SHORT ENTRY
// ============================================================================

if finalShortEntry

    pendingRiskDistance := shortRiskDistance

    strategy.entry(
         "Short",
         strategy.short,
         qty=positionContracts,
         comment=entryMethod == "FVG Pullback" ? "FVG Short" : "EMA Short",
         alert_message="ENTRY_SHORT_CNPS44_V2"
    )

    tradesToday += 1


// ============================================================================
// SECTION 45: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na
var float activeInitialRisk = na
var float activeStopPrice = na
var float activeTargetPrice = na
var bool breakEvenActivated = false


// ============================================================================
// SECTION 46: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeInitialRisk := pendingRiskDistance

    activeStopPrice := (
         activeEntryPrice -
         activeInitialRisk
    )

    if riskMode == "Fixed Points"
        activeTargetPrice := activeEntryPrice + fixedTakeProfitPoints

    else
        activeTargetPrice := activeEntryPrice + activeInitialRisk * rewardRisk

    breakEvenActivated := false

    pendingRiskDistance := na


// ============================================================================
// SECTION 47: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeInitialRisk := pendingRiskDistance

    activeStopPrice := (
         activeEntryPrice +
         activeInitialRisk
    )

    if riskMode == "Fixed Points"
        activeTargetPrice := activeEntryPrice - fixedTakeProfitPoints

    else
        activeTargetPrice := activeEntryPrice - activeInitialRisk * rewardRisk

    breakEvenActivated := false

    pendingRiskDistance := na


// ============================================================================
// SECTION 48: LONG BREAK EVEN
// ============================================================================

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

    float longBETrigger = (
         activeEntryPrice +
         activeInitialRisk *
         breakEvenTriggerR
    )

    if high >= longBETrigger

        float candidateStop = (
             activeEntryPrice +
             breakEvenOffsetPoints
        )

        activeStopPrice := math.max(
             activeStopPrice,
             candidateStop
        )

        breakEvenActivated := true


// ============================================================================
// SECTION 49: SHORT BREAK EVEN
// ============================================================================

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

    float shortBETrigger = (
         activeEntryPrice -
         activeInitialRisk *
         breakEvenTriggerR
    )

    if low <= shortBETrigger

        float candidateStop = (
             activeEntryPrice -
             breakEvenOffsetPoints
        )

        activeStopPrice := math.min(
             activeStopPrice,
             candidateStop
        )

        breakEvenActivated := true


// ============================================================================
// SECTION 50: LONG PROTECTIVE EXIT
// ============================================================================

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

    strategy.exit(
         "Long Risk Exit",
         from_entry="Long",
         stop=activeStopPrice,
         limit=activeTargetPrice,
         alert_profit="EXIT_LONG_TP_CNPS44_V2",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS44_V2" : "EXIT_LONG_SL_CNPS44_V2",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 51: SHORT PROTECTIVE EXIT
// ============================================================================

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

    strategy.exit(
         "Short Risk Exit",
         from_entry="Short",
         stop=activeStopPrice,
         limit=activeTargetPrice,
         alert_profit="EXIT_SHORT_TP_CNPS44_V2",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS44_V2" : "EXIT_SHORT_SL_CNPS44_V2",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 52: OPPOSITE SIGNAL - LONG
// ============================================================================

bool longOppositeSignal = (
     inLong and
     sourceSellSignal and
     exitOnOppositeSignal and
     barstate.isconfirmed
)

if longOppositeSignal

    strategy.close(
         "Long",
         comment="Opposite Signal",
         alert_message="OPPOSITE_EXIT_LONG_CNPS44_V2"
    )


// ============================================================================
// SECTION 53: OPPOSITE SIGNAL - SHORT
// ============================================================================

bool shortOppositeSignal = (
     inShort and
     sourceBuySignal and
     exitOnOppositeSignal and
     barstate.isconfirmed
)

if shortOppositeSignal

    strategy.close(
         "Short",
         comment="Opposite Signal",
         alert_message="OPPOSITE_EXIT_SHORT_CNPS44_V2"
    )


// ============================================================================
// SECTION 54: OPTIONAL IMMEDIATE REVERSAL
// ============================================================================

bool reverseLongToShort = (
     inLong and
     sourceSellSignal and
     reverseOnOppositeSignal and
     allowShort and
     entrySession and
     dailyTradeLimitOK and
     shortRiskOK
)

bool reverseShortToLong = (
     inShort and
     sourceBuySignal and
     reverseOnOppositeSignal and
     allowLong and
     entrySession and
     dailyTradeLimitOK and
     longRiskOK
)

if reverseLongToShort

    pendingRiskDistance := shortRiskDistance

    strategy.entry(
         "Short",
         strategy.short,
         qty=positionContracts,
         comment="Reverse Short",
         alert_message="REVERSE_SHORT_CNPS44_V2"
    )

    tradesToday += 1


if reverseShortToLong

    pendingRiskDistance := longRiskDistance

    strategy.entry(
         "Long",
         strategy.long,
         qty=positionContracts,
         comment="Reverse Long",
         alert_message="REVERSE_LONG_CNPS44_V2"
    )

    tradesToday += 1


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


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

if positionJustClosed

    activeEntryPrice := na
    activeInitialRisk := na
    activeStopPrice := na
    activeTargetPrice := na
    breakEvenActivated := false


// ============================================================================
// SECTION 57: EMA50
// ============================================================================

plot(
     showEMA50 ? ema50 : na,
     title="EMA 50",
     color=color.gray,
     linewidth=2
)


// ============================================================================
// SECTION 58: FAST EMA
// ============================================================================

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


// ============================================================================
// SECTION 59: SLOW EMA
// ============================================================================

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


// ============================================================================
// SECTION 60: FVG DISPLAY
// ============================================================================

plot(
     showFVG and not na(bullishFVGBar) ? bullishFVGTop : na,
     title="Bull FVG Top",
     color=color.green,
     style=plot.style_linebr
)

plot(
     showFVG and not na(bullishFVGBar) ? bullishFVGBottom : na,
     title="Bull FVG Bottom",
     color=color.new(color.green, 50),
     style=plot.style_linebr
)

plot(
     showFVG and not na(bearishFVGBar) ? bearishFVGTop : na,
     title="Bear FVG Top",
     color=color.red,
     style=plot.style_linebr
)

plot(
     showFVG and not na(bearishFVGBar) ? bearishFVGBottom : na,
     title="Bear FVG Bottom",
     color=color.new(color.red, 50),
     style=plot.style_linebr
)


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


// ============================================================================
// SECTION 62: SIGNAL MARKERS
// ============================================================================

plotshape(
     showSignalMarkers and sourceBuySignal,
     title="Buy Signal",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.small,
     text="BUY"
)

plotshape(
     showSignalMarkers and sourceSellSignal,
     title="Sell Signal",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.small,
     text="SELL"
)


// ============================================================================
// SECTION 63: DATA WINDOW
// ============================================================================

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

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

plot(
     bodyRatio,
     title="Confirmation Body Ratio",
     display=display.data_window
)

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


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

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

if (
     showDiagnostics and
     barstate.islast
)

    string positionText = "FLAT"

    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


    string htfBiasText = "NEUTRAL"

    if higherTimeframeBullish
        htfBiasText := "BULL"

    else if higherTimeframeBearish
        htfBiasText := "BEAR"


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

    table.cell(diagnostics, 0, 1, "Engine")
    table.cell(diagnostics, 1, 1, entryMethod)

    table.cell(diagnostics, 0, 2, "Sensitivity")
    table.cell(diagnostics, 1, 2, sensitivity)

    table.cell(diagnostics, 0, 3, "EMA Pair")
    table.cell(
         diagnostics,
         1,
         3,
         str.tostring(fastEMALength) + "/" + str.tostring(slowEMALength)
    )

    table.cell(diagnostics, 0, 4, "EMA50 Slope")
    table.cell(
         diagnostics,
         1,
         4,
         ema50SlopeBullish ? "UP" : ema50SlopeBearish ? "DOWN" : "FLAT"
    )

    table.cell(diagnostics, 0, 5, "HTF")
    table.cell(diagnostics, 1, 5, higherTimeframe)

    table.cell(diagnostics, 0, 6, "HTF Bias")
    table.cell(diagnostics, 1, 6, htfBiasText)

    table.cell(diagnostics, 0, 7, "ADX")
    table.cell(diagnostics, 1, 7, str.tostring(adxValue, "0.00"))

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

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

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

    table.cell(diagnostics, 0, 11, "Risk Mode")
    table.cell(diagnostics, 1, 11, riskMode)

    table.cell(diagnostics, 0, 12, "Initial Risk")
    table.cell(
         diagnostics,
         1,
         12,
         str.tostring(activeInitialRisk, "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, "Cooldown")
    table.cell(diagnostics, 1, 14, cooldownOK ? "READY" : "WAIT")

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

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