//@version=6
strategy(
     title="CNPS25 FVG + BOS CHOCH RETEST",
     shorttitle="CNPS25 FVG STRUCTURE",
     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
)


// ============================================================================
// CNPS25 FVG + BOS / CHOCH RETEST
//
// SOURCE ATTRIBUTION:
//
// Structure logic:
// "Structure BOS / CHoCH" - Elijah's Structure
// MPL-2.0
//
// FVG logic:
// "Elijah's FVG" derived from LuxAlgo Fair Value Gap
// CC BY-NC-SA 4.0
//
// The supplied source states that the combined work is non-commercial and
// redistribution must preserve the required attribution / license terms.
//
// CNPS ADAPTATION:
//
// STRUCTURE BREAK
//       ↓
// FVG FORMS AFTER STRUCTURE
//       ↓
// FVG ALIGNS WITH STRUCTURE
//       ↓
// FVG SIZE FILTER
//       ↓
// >= 50% MITIGATION
//       ↓
// CLOSE BEYOND FVG
// + CLOSE BEYOND STRUCTURE
//       ↓
// ENTRY
//
// DEFAULT EXIT:
// SL = MIDDLE CANDLE EXTREME
// TP = 3R
// BE = 50% OF DISTANCE TO TP
//
// EXECUTION:
// 100M VND
// 3 CONTRACTS
// COMMISSION = 10,000 / CONTRACT
// SLIPPAGE = 3 TICKS
// CURRENT CHART TIMEFRAME
// ============================================================================


// ============================================================================
// SECTION 1: TRADE CONTROL
// ============================================================================

tradeDirection = input.string(
     "Both",
     title="Trade Direction",
     options=["Long", "Short", "Both"],
     group="Trade Control"
)

bool allowLong = tradeDirection == "Long" or tradeDirection == "Both"
bool allowShort = tradeDirection == "Short" or tradeDirection == "Both"

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

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


// ============================================================================
// SECTION 2: FVG RULES
// ============================================================================

minimumFvgTicks = input.int(
     5,
     title="Minimum FVG Size (Ticks)",
     minval=1,
     group="FVG Rules"
)

maximumFvgTicks = input.int(
     10,
     title="Maximum FVG Size (Ticks)",
     minval=1,
     group="FVG Rules"
)

minimumMitigationPercent = input.float(
     50.0,
     title="Minimum Mitigation %",
     minval=0.0,
     maxval=100.0,
     step=5.0,
     group="FVG Rules"
)

requireStructureAlignment = input.bool(
     true,
     title="Require BOS / CHoCH Alignment?",
     group="FVG Rules"
)

requireFvgClose = input.bool(
     true,
     title="Require Close Beyond FVG?",
     group="FVG Rules"
)

requireStructureClose = input.bool(
     true,
     title="Require Close Beyond Structure?",
     group="FVG Rules"
)

requireFvgAfterStructure = input.bool(
     true,
     title="FVG Must Form After Structure Event?",
     group="FVG Rules"
)

oneTradePerFvg = input.bool(
     true,
     title="One Trade Per FVG?",
     group="FVG Rules"
)

maximumTrackedFvgs = input.int(
     200,
     title="Maximum Tracked FVGs",
     minval=10,
     maxval=1000,
     group="FVG Rules"
)


// ============================================================================
// SECTION 3: OPTIONAL FVG THRESHOLD
// ============================================================================

fvgThresholdPercent = input.float(
     0.0,
     title="Minimum FVG Gap %",
     minval=0.0,
     maxval=100.0,
     step=0.1,
     group="FVG Detection"
)

autoFvgThreshold = input.bool(
     false,
     title="Auto FVG Threshold?",
     group="FVG Detection"
)


// ============================================================================
// SECTION 4: MARKET STRUCTURE
// ============================================================================

swingLength = input.int(
     5,
     title="Swing Length",
     minval=2,
     maxval=50,
     group="Market Structure"
)


// ============================================================================
// SECTION 5: EXIT MODE
// ============================================================================

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


// ============================================================================
// SECTION 6: SOURCE-STYLE STRUCTURE EXIT
// ============================================================================

rewardRiskRatio = input.float(
     3.0,
     title="Take Profit R Multiple",
     minval=0.5,
     maxval=10.0,
     step=0.25,
     group="Risk Management"
)

maximumStructureRiskPoints = input.float(
     0.0,
     title="Maximum Structure Risk (0 = Off)",
     minval=0.0,
     step=0.5,
     group="Risk Management"
)


// ============================================================================
// SECTION 7: FIXED EXIT
// ============================================================================

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

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


// ============================================================================
// SECTION 8: BREAK EVEN
// ============================================================================

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

breakEvenTriggerPercent = input.float(
     50.0,
     title="Trigger % Of Distance To TP",
     minval=1.0,
     maxval=99.0,
     step=5.0,
     group="Break Even"
)

breakEvenRequireClose = input.bool(
     false,
     title="Require Candle Close Beyond Trigger?",
     group="Break Even"
)

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


// ============================================================================
// SECTION 9: SESSION
// ============================================================================

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

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

sessionStartMinute = input.int(
     0,
     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"
)

sessionEndHour = input.int(
     14,
     title="Force Close Hour",
     minval=0,
     maxval=23,
     group="Session"
)

sessionEndMinute = input.int(
     45,
     title="Force Close Minute",
     minval=0,
     maxval=59,
     group="Session"
)


// ============================================================================
// SECTION 10: ALERTS
// ============================================================================

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


// ============================================================================
// SECTION 11: DISPLAY
// ============================================================================

showFvgMarkers = input.bool(
     false,
     title="Show FVG Markers?",
     group="Display"
)

showStructureMarkers = input.bool(
     false,
     title="Show BOS / CHoCH Markers?",
     group="Display"
)

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

showActiveStructure = input.bool(
     false,
     title="Show Active Structure Level?",
     group="Display"
)

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

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


// ============================================================================
// SECTION 12: FVG SETUP TYPE
// ============================================================================
//
// state:
// 0 = waiting for mitigation
// 1 = mitigated, waiting for close confirmations
// 2 = traded
// 3 = expired by newer structure event
//
// ============================================================================

type FvgSetup
    float top
    float bot
    bool isBull
    int formBar
    float middleHigh
    float middleLow
    int ticks
    bool mitigated
    bool traded
    int state


var array<FvgSetup> setups = array.new<FvgSetup>()


// ============================================================================
// SECTION 13: TIME ENGINE
// ============================================================================

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

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

int sessionStartTotal = sessionStartHour * 60 + sessionStartMinute

int lastEntryTotal = lastEntryHour * 60 + lastEntryMinute

int sessionEndTotal = sessionEndHour * 60 + sessionEndMinute

bool entrySession = currentMinute >= sessionStartTotal and currentMinute <= lastEntryTotal


// ============================================================================
// SECTION 14: DAILY TRADE 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 15: 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 16: COOLDOWN
// ============================================================================

var int lastExitBar = na

if positionJustClosed
    lastExitBar := bar_index

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

bool tradeLimitOK = tradesToday < maximumTradesPerDay


// ============================================================================
// SECTION 17: MARKET STRUCTURE STATE
// ============================================================================

var float lastHigh = na

var int lastHighBar = na

var bool highTaken = true

var float lastLow = na

var int lastLowBar = na

var bool lowTaken = true

var int trendDirection = 0


// ============================================================================
// SECTION 18: SWING DETECTION
// ============================================================================

float pivotHigh = ta.pivothigh(
     high,
     swingLength,
     swingLength
)

float pivotLow = ta.pivotlow(
     low,
     swingLength,
     swingLength
)


if not na(pivotHigh)

    lastHigh := pivotHigh

    lastHighBar := bar_index - swingLength

    highTaken := false


if not na(pivotLow)

    lastLow := pivotLow

    lastLowBar := bar_index - swingLength

    lowTaken := false


// ============================================================================
// SECTION 19: STRUCTURE BREAK EVENTS
// ============================================================================

bool bullishBreak = (
     not na(lastHigh) and
     not highTaken and
     close > lastHigh
)

bool bearishBreak = (
     not na(lastLow) and
     not lowTaken and
     close < lastLow
)


bool bullishBosEvent = false

bool bullishChochEvent = false

bool bearishBosEvent = false

bool bearishChochEvent = false

float bullishBreakLevel = na

float bearishBreakLevel = na


if bullishBreak

    highTaken := true

    bool isBullBos = trendDirection == 1

    bullishBosEvent := isBullBos

    bullishChochEvent := not isBullBos

    bullishBreakLevel := lastHigh

    if not isBullBos
        trendDirection := 1


if bearishBreak

    lowTaken := true

    bool isBearBos = trendDirection == -1

    bearishBosEvent := isBearBos

    bearishChochEvent := not isBearBos

    bearishBreakLevel := lastLow

    if not isBearBos
        trendDirection := -1


// ============================================================================
// SECTION 20: LATEST STRUCTURE EVENT
// ============================================================================

var int structureDirection = 0

var float structureLevel = na

var int structureBar = na

var float lastBullishStructureLevel = na

var float lastBearishStructureLevel = na


if bullishBreak or bearishBreak

    if bullishBreak
        lastBullishStructureLevel := bullishBreakLevel

    if bearishBreak
        lastBearishStructureLevel := bearishBreakLevel


    if bullishBreak and bearishBreak

        structureDirection := 0

        structureLevel := na

    else

        structureDirection := bullishBreak ? 1 : -1

        structureLevel := bullishBreak ? bullishBreakLevel : bearishBreakLevel


    structureBar := bar_index


    // Expire pending FVGs when a newer structure event supersedes them.

    int structureSetupCount = array.size(setups)

    if structureSetupCount > 0

        for i = 0 to structureSetupCount - 1

            FvgSetup setup = array.get(
                 setups,
                 i
            )

            if setup.state == 0 or setup.state == 1

                bool opposingStructure = (
                     structureDirection == 0 or
                     (
                         setup.isBull and
                         structureDirection == -1
                     ) or
                     (
                         not setup.isBull and
                         structureDirection == 1
                     )
                )


                if requireFvgAfterStructure or (requireStructureAlignment and opposingStructure)

                    setup.state := 3

                    array.set(
                         setups,
                         i,
                         setup
                    )


// ============================================================================
// SECTION 21: FVG THRESHOLD ENGINE
// ============================================================================

var float cumulativeRangeRatio = 0.0

float currentRangeRatio = 0.0

if low != 0
    currentRangeRatio := (high - low) / low

cumulativeRangeRatio += currentRangeRatio


float activeFvgThreshold = fvgThresholdPercent / 100.0

if autoFvgThreshold and bar_index > 0
    activeFvgThreshold := cumulativeRangeRatio / bar_index


// ============================================================================
// SECTION 22: FVG DETECTION
// ============================================================================
//
// Bullish FVG:
//
// Current Low > High[2]
// Middle Candle Close > High[2]
//
// Bearish FVG:
//
// Current High < Low[2]
// Middle Candle Close < Low[2]
//
// ============================================================================

bool bullishFvg = (
     bar_index >= 2 and
     low > high[2] and
     close[1] > high[2] and
     (low - high[2]) / high[2] > activeFvgThreshold
)

bool bearishFvg = (
     bar_index >= 2 and
     high < low[2] and
     close[1] < low[2] and
     (low[2] - high) / high > activeFvgThreshold
)


// ============================================================================
// SECTION 23: CREATE BULLISH FVG
// ============================================================================

if bullishFvg

    float newTop = low

    float newBottom = high[2]

    int newTicks = int(
         math.round(
             (newTop - newBottom) /
             syminfo.mintick
         )
    )

    bool sizeOK = newTicks >= minimumFvgTicks and newTicks <= maximumFvgTicks

    bool alignmentOK = not requireStructureAlignment or structureDirection == 1

    bool afterStructureOK = (
         not requireFvgAfterStructure or
         (
             not na(structureBar) and
             bar_index > structureBar
         )
    )

    bool eligible = (
         sizeOK and
         alignmentOK and
         afterStructureOK
    )


    if eligible

        FvgSetup newSetup = FvgSetup.new(
             newTop,
             newBottom,
             true,
             bar_index,
             high[1],
             low[1],
             newTicks,
             false,
             false,
             0
        )

        array.unshift(
             setups,
             newSetup
        )


// ============================================================================
// SECTION 24: CREATE BEARISH FVG
// ============================================================================

if bearishFvg

    float newTop = low[2]

    float newBottom = high

    int newTicks = int(
         math.round(
             (newTop - newBottom) /
             syminfo.mintick
         )
    )

    bool sizeOK = newTicks >= minimumFvgTicks and newTicks <= maximumFvgTicks

    bool alignmentOK = not requireStructureAlignment or structureDirection == -1

    bool afterStructureOK = (
         not requireFvgAfterStructure or
         (
             not na(structureBar) and
             bar_index > structureBar
         )
    )

    bool eligible = (
         sizeOK and
         alignmentOK and
         afterStructureOK
    )


    if eligible

        FvgSetup newSetup = FvgSetup.new(
             newTop,
             newBottom,
             false,
             bar_index,
             high[1],
             low[1],
             newTicks,
             false,
             false,
             0
        )

        array.unshift(
             setups,
             newSetup
        )


// ============================================================================
// SECTION 25: CAP TRACKED FVGS
// ============================================================================

while array.size(setups) > maximumTrackedFvgs
    array.pop(setups)


// ============================================================================
// SECTION 26: CANDIDATE SEARCH
// ============================================================================
//
// Scan from oldest -> newest.
//
// Since newest FVG is index 0, the final matching assignment is the
// newest valid candidate.
//
// ============================================================================

int longCandidateIndex = na

int shortCandidateIndex = na


int setupCount = array.size(setups)


if setupCount > 0

    for i = setupCount - 1 to 0

        FvgSetup setup = array.get(
             setups,
             i
        )


        bool activeSetup = (
             setup.state == 0 or
             setup.state == 1
        )


        bool formationFinished = (
             bar_index >
             setup.formBar
        )


        if activeSetup and formationFinished

            float fvgRange = setup.top - setup.bot


            if setup.isBull

                float mitigationLevel = setup.top - fvgRange * minimumMitigationPercent / 100.0


                if (
                     not setup.mitigated and
                     low <= mitigationLevel
                )

                    setup.mitigated := true

                    setup.state := 1


                if setup.mitigated

                    float confirmationStructureLevel = requireStructureAlignment ? structureLevel : lastBullishStructureLevel

                    bool alignmentOK = (
                         not requireStructureAlignment or
                         structureDirection == 1
                    )

                    bool fvgCloseOK = (
                         not requireFvgClose or
                         close > setup.top
                    )

                    bool structureCloseOK = (
                         not requireStructureClose or
                         (
                             not na(
                                 confirmationStructureLevel
                             ) and
                             close >
                             confirmationStructureLevel
                         )
                    )


                    if (
                         alignmentOK and
                         fvgCloseOK and
                         structureCloseOK
                    )

                        longCandidateIndex := i


            else

                float mitigationLevel = setup.bot + fvgRange * minimumMitigationPercent / 100.0


                if (
                     not setup.mitigated and
                     high >= mitigationLevel
                )

                    setup.mitigated := true

                    setup.state := 1


                if setup.mitigated

                    float confirmationStructureLevel = requireStructureAlignment ? structureLevel : lastBearishStructureLevel

                    bool alignmentOK = (
                         not requireStructureAlignment or
                         structureDirection == -1
                    )

                    bool fvgCloseOK = (
                         not requireFvgClose or
                         close < setup.bot
                    )

                    bool structureCloseOK = (
                         not requireStructureClose or
                         (
                             not na(
                                 confirmationStructureLevel
                             ) and
                             close <
                             confirmationStructureLevel
                         )
                    )


                    if (
                         alignmentOK and
                         fvgCloseOK and
                         structureCloseOK
                    )

                        shortCandidateIndex := i


            array.set(
                 setups,
                 i,
                 setup
            )


// ============================================================================
// SECTION 27: ENTRY APPROVAL
// ============================================================================

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

bool conflict = (
     not na(longCandidateIndex) and
     not na(shortCandidateIndex)
)


// ============================================================================
// SECTION 28: CANDIDATE RISK VALIDATION
// ============================================================================

bool longRiskOK = false

bool shortRiskOK = false


float candidateLongStop = na

float candidateShortStop = na


if not na(longCandidateIndex)

    FvgSetup longSetup = array.get(
         setups,
         longCandidateIndex
    )

    candidateLongStop := longSetup.middleLow

    float estimatedLongEntry = close + 3.0 * syminfo.mintick

    float estimatedLongRisk = estimatedLongEntry - candidateLongStop

    longRiskOK := (
         estimatedLongRisk >=
         syminfo.mintick
    )


    if (
         maximumStructureRiskPoints >
         0 and
         estimatedLongRisk >
         maximumStructureRiskPoints
    )

        longRiskOK := false


if not na(shortCandidateIndex)

    FvgSetup shortSetup = array.get(
         setups,
         shortCandidateIndex
    )

    candidateShortStop := shortSetup.middleHigh

    float estimatedShortEntry = close - 3.0 * syminfo.mintick

    float estimatedShortRisk = candidateShortStop - estimatedShortEntry

    shortRiskOK := (
         estimatedShortRisk >=
         syminfo.mintick
    )


    if (
         maximumStructureRiskPoints >
         0 and
         estimatedShortRisk >
         maximumStructureRiskPoints
    )

        shortRiskOK := false


// ============================================================================
// SECTION 29: FINAL ENTRY SIGNALS
// ============================================================================

bool finalLongSignal = (
     commonEntryApproval and
     allowLong and
     not conflict and
     not na(longCandidateIndex) and
     longRiskOK
)

bool finalShortSignal = (
     commonEntryApproval and
     allowShort and
     not conflict and
     not na(shortCandidateIndex) and
     shortRiskOK
)


// ============================================================================
// SECTION 30: PENDING STOPS
// ============================================================================

var float pendingLongStop = na

var float pendingShortStop = na


// ============================================================================
// SECTION 31: LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingLongStop := candidateLongStop


    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment="FVG BOS Long",
         alert_message="ENTRY_LONG_CNPS25"
    )


    FvgSetup tradedSetup = array.get(
         setups,
         longCandidateIndex
    )

    tradedSetup.traded := true

    tradedSetup.state := oneTradePerFvg ? 2 : 1

    array.set(
         setups,
         longCandidateIndex,
         tradedSetup
    )


    tradesToday += 1


// ============================================================================
// SECTION 32: SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingShortStop := candidateShortStop


    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment="FVG BOS Short",
         alert_message="ENTRY_SHORT_CNPS25"
    )


    FvgSetup tradedSetup = array.get(
         setups,
         shortCandidateIndex
    )

    tradedSetup.traded := true

    tradedSetup.state := oneTradePerFvg ? 2 : 1

    array.set(
         setups,
         shortCandidateIndex,
         tradedSetup
    )


    tradesToday += 1


// ============================================================================
// SECTION 33: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeInitialRisk = na

var float activeLongSL = na

var float activeLongTP = na

var float activeShortSL = na

var float activeShortTP = na

var bool breakEvenActivated = false


// ============================================================================
// SECTION 34: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price


    if exitMode == "Middle Candle RR"

        float structureRisk = activeEntryPrice - pendingLongStop


        if (
             na(structureRisk) or
             structureRisk <=
             0
        )

            structureRisk := fixedStopLossPoints


        activeInitialRisk := structureRisk

        activeLongSL := activeEntryPrice - activeInitialRisk

        activeLongTP := activeEntryPrice + activeInitialRisk * rewardRiskRatio


    else

        activeInitialRisk := fixedStopLossPoints

        activeLongSL := activeEntryPrice - fixedStopLossPoints

        activeLongTP := activeEntryPrice + fixedTakeProfitPoints


    activeShortSL := na

    activeShortTP := na

    breakEvenActivated := false

    pendingLongStop := na


// ============================================================================
// SECTION 35: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price


    if exitMode == "Middle Candle RR"

        float structureRisk = pendingShortStop - activeEntryPrice


        if (
             na(structureRisk) or
             structureRisk <=
             0
        )

            structureRisk := fixedStopLossPoints


        activeInitialRisk := structureRisk

        activeShortSL := activeEntryPrice + activeInitialRisk

        activeShortTP := activeEntryPrice - activeInitialRisk * rewardRiskRatio


    else

        activeInitialRisk := fixedStopLossPoints

        activeShortSL := activeEntryPrice + fixedStopLossPoints

        activeShortTP := activeEntryPrice - fixedTakeProfitPoints


    activeLongSL := na

    activeLongTP := na

    breakEvenActivated := false

    pendingShortStop := na


// ============================================================================
// SECTION 36: LONG BREAK EVEN
// ============================================================================

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

    float longBreakEvenTrigger = activeEntryPrice + (activeLongTP - activeEntryPrice) * breakEvenTriggerPercent / 100.0


    bool longBreakEvenHit = (
         breakEvenRequireClose
         ? close >= longBreakEvenTrigger
         : high >= longBreakEvenTrigger
    )


    if longBreakEvenHit

        float newLongStop = activeEntryPrice + breakEvenOffsetPoints


        if (
             na(activeLongSL) or
             newLongStop >
             activeLongSL
        )

            activeLongSL := newLongStop


        breakEvenActivated := true


// ============================================================================
// SECTION 37: SHORT BREAK EVEN
// ============================================================================

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

    float shortBreakEvenTrigger = activeEntryPrice - (activeEntryPrice - activeShortTP) * breakEvenTriggerPercent / 100.0


    bool shortBreakEvenHit = (
         breakEvenRequireClose
         ? close <= shortBreakEvenTrigger
         : low <= shortBreakEvenTrigger
    )


    if shortBreakEvenHit

        float newShortStop = activeEntryPrice - breakEvenOffsetPoints


        if (
             na(activeShortSL) or
             newShortStop <
             activeShortSL
        )

            activeShortSL := newShortStop


        breakEvenActivated := true


// ============================================================================
// SECTION 38: LONG EXIT
// ============================================================================

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

    string longLossAlert = breakEvenActivated ? "EXIT_LONG_BE_CNPS25" : "EXIT_LONG_SL_CNPS25"


    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=activeLongSL,
         limit=activeLongTP,
         alert_profit="EXIT_LONG_TP_CNPS25",
         alert_loss=longLossAlert,
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 39: SHORT EXIT
// ============================================================================

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

    string shortLossAlert = breakEvenActivated ? "EXIT_SHORT_BE_CNPS25" : "EXIT_SHORT_SL_CNPS25"


    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=activeShortSL,
         limit=activeShortTP,
         alert_profit="EXIT_SHORT_TP_CNPS25",
         alert_loss=shortLossAlert,
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 40: POSITION CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeInitialRisk := na

    activeLongSL := na

    activeLongTP := na

    activeShortSL := na

    activeShortTP := na

    breakEvenActivated := false


// ============================================================================
// SECTION 41: FVG INVALIDATION
// ============================================================================
//
// Bullish FVG invalidates if Close < FVG bottom.
//
// Bearish FVG invalidates if Close > FVG top.
//
// ============================================================================

int invalidationCount = array.size(setups)


if invalidationCount > 0

    for i = invalidationCount - 1 to 0

        FvgSetup setup = array.get(
             setups,
             i
        )


        bool invalidated = (
             (
                 setup.isBull and
                 close <
                 setup.bot
             ) or
             (
                 not setup.isBull and
                 close >
                 setup.top
             )
        )


        if invalidated

            array.remove(
                 setups,
                 i
            )


// ============================================================================
// SECTION 42: SESSION END
// ============================================================================

bool finalSessionBar = currentMinute < sessionEndTotal and currentCloseMinute >= sessionEndTotal


if finalSessionBar

    if strategy.position_size != 0

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


// ============================================================================
// SECTION 43: DISPLAY - STRUCTURE
// ============================================================================

plot(
     showActiveStructure
     ? structureLevel
     : na,
     title="Active Structure Level",
     color=structureDirection == 1 ? color.green : color.red,
     linewidth=1,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 44: DISPLAY - STRUCTURE EVENTS
// ============================================================================

plotshape(
     showStructureMarkers and bullishBosEvent,
     title="Bullish BOS",
     style=shape.circle,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="BOS"
)

plotshape(
     showStructureMarkers and bullishChochEvent,
     title="Bullish CHoCH",
     style=shape.diamond,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="CH"
)

plotshape(
     showStructureMarkers and bearishBosEvent,
     title="Bearish BOS",
     style=shape.circle,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="BOS"
)

plotshape(
     showStructureMarkers and bearishChochEvent,
     title="Bearish CHoCH",
     style=shape.diamond,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="CH"
)


// ============================================================================
// SECTION 45: DISPLAY - FVG
// ============================================================================

plotshape(
     showFvgMarkers and bullishFvg,
     title="Bullish FVG",
     style=shape.square,
     location=location.belowbar,
     color=color.teal,
     size=size.tiny,
     text="F"
)

plotshape(
     showFvgMarkers and bearishFvg,
     title="Bearish FVG",
     style=shape.square,
     location=location.abovebar,
     color=color.orange,
     size=size.tiny,
     text="F"
)


// ============================================================================
// SECTION 46: DISPLAY - ENTRY
// ============================================================================

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

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


// ============================================================================
// SECTION 47: ACTIVE SL / TP
// ============================================================================

plot(
     showActiveSLTP and inLong ? activeLongSL : na,
     title="Long SL",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)

plot(
     showActiveSLTP and inLong ? activeLongTP : na,
     title="Long TP",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)

plot(
     showActiveSLTP and inShort ? activeShortSL : na,
     title="Short SL",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)

plot(
     showActiveSLTP and inShort ? activeShortTP : na,
     title="Short TP",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 48: DIAGNOSTIC COUNTERS
// ============================================================================

var int bullishFvgCount = 0

var int bearishFvgCount = 0

var int bullishBosCount = 0

var int bearishBosCount = 0

var int bullishChochCount = 0

var int bearishChochCount = 0

var int longEntryCount = 0

var int shortEntryCount = 0


if bullishFvg
    bullishFvgCount += 1


if bearishFvg
    bearishFvgCount += 1


if bullishBosEvent
    bullishBosCount += 1


if bearishBosEvent
    bearishBosCount += 1


if bullishChochEvent
    bullishChochCount += 1


if bearishChochEvent
    bearishChochCount += 1


if finalLongSignal
    longEntryCount += 1


if finalShortSignal
    shortEntryCount += 1


// ============================================================================
// SECTION 49: 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 structureText = "NONE"

    if structureDirection == 1
        structureText := "BULL"

    else if structureDirection == -1
        structureText := "BEAR"


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


    table.cell(diagnostics, 0, 1, "Structure")
    table.cell(diagnostics, 1, 1, structureText)


    table.cell(diagnostics, 0, 2, "Structure Level")
    table.cell(diagnostics, 1, 2, str.tostring(structureLevel, format.mintick))


    table.cell(diagnostics, 0, 3, "Tracked FVGs")
    table.cell(diagnostics, 1, 3, str.tostring(array.size(setups)))


    table.cell(diagnostics, 0, 4, "Bull FVG")
    table.cell(diagnostics, 1, 4, str.tostring(bullishFvgCount))


    table.cell(diagnostics, 0, 5, "Bear FVG")
    table.cell(diagnostics, 1, 5, str.tostring(bearishFvgCount))


    table.cell(diagnostics, 0, 6, "Bull BOS")
    table.cell(diagnostics, 1, 6, str.tostring(bullishBosCount))


    table.cell(diagnostics, 0, 7, "Bear BOS")
    table.cell(diagnostics, 1, 7, str.tostring(bearishBosCount))


    table.cell(diagnostics, 0, 8, "Bull CHoCH")
    table.cell(diagnostics, 1, 8, str.tostring(bullishChochCount))


    table.cell(diagnostics, 0, 9, "Bear CHoCH")
    table.cell(diagnostics, 1, 9, str.tostring(bearishChochCount))


    table.cell(diagnostics, 0, 10, "Long Candidate")
    table.cell(diagnostics, 1, 10, not na(longCandidateIndex) ? "YES" : "NO")


    table.cell(diagnostics, 0, 11, "Short Candidate")
    table.cell(diagnostics, 1, 11, not na(shortCandidateIndex) ? "YES" : "NO")


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


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


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


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


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