//@version=6
strategy(
     title="CNPS30 SMART MONEY LIQUIDITY OB",
     shorttitle="CNPS30 SML OB",
     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
)

import TFlab/OrderBlockRefiner_TradingFinder/2 as Refiner


// ============================================================================
// CNPS30 SMART MONEY LIQUIDITY ORDER BLOCK
//
// SOURCE-DERIVED ENTRY ENGINE:
//
// PIVOT STRUCTURE
//      ↓
// HH / HL / LH / LL SEQUENCE
//      ↓
// ┌───────────────────────────────────────────────────────────────────────┐
// │ ONE LIQUIDITY SETUP                                                  │
// │ TWO LIQUIDITY SETUP                                                  │
// │ MIDWAY LIQUIDITY SETUP                                               │
// └───────────────────────────────────────────────────────────────────────┘
//      ↓
// REFINED DEMAND / SUPPLY ORDER BLOCK
//      ↓
// FIRST PRICE RETEST / OVERLAP
//      ↓
// ENTRY
//
// CNPS EXECUTION:
// - Current chart timeframe
// - 100M VND
// - 3 contracts
// - Commission 10,000 VND / contract
// - Slippage 3 ticks
// - Entry 09:00 -> 14:30
// - Force close 14:45
//
// DEFAULT EXIT:
// - SL 5 points
// - TP 15 points
//
// OPTIONAL:
// - OB Structure RR
// ============================================================================


// ============================================================================
// SECTION 1: SOURCE DETECTION
// ============================================================================

pivotPeriod = input.int(
     3,
     title="One & Two Setup Pivot Period",
     minval=2,
     maxval=100,
     group="Detection"
)

midwayPivotPeriod = input.int(
     9,
     title="Midway Liquidity Pivot Period",
     minval=2,
     maxval=100,
     group="Detection"
)

enableOneSetup = input.bool(
     true,
     title="One Liquidity Setup",
     group="Detection"
)

enableTwoSetup = input.bool(
     true,
     title="Two Liquidity Setup",
     group="Detection"
)

enableMidwaySetup = input.bool(
     true,
     title="Midway Liquidity Setup",
     group="Detection"
)

midwayQualityMode = input.string(
     "Defensive",
     title="Midway Liquidity Mode",
     options=["Aggressive", "Moderate", "Defensive"],
     group="Detection"
)


// ============================================================================
// SECTION 2: ORDER BLOCK SETTINGS
// ============================================================================

orderBlockValidityBars = input.int(
     1000,
     title="OB Validity Bars",
     minval=10,
     maxval=4998,
     group="Order Blocks"
)

mitigationLevel = input.string(
     "Proximal",
     title="OB Mitigation Level",
     options=["Proximal", "50 % OB", "Distal"],
     group="Order Blocks"
)

refineOrderBlock = input.bool(
     true,
     title="Refine OB",
     group="Order Blocks"
)

refineMode = input.string(
     "Defensive",
     title="OB Refinement Mode",
     options=["Defensive", "Aggressive"],
     group="Order Blocks"
)


// ============================================================================
// SECTION 3: 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(
     4,
     title="Maximum Trades Per Day",
     minval=1,
     maxval=20,
     group="Trade Control"
)

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


// ============================================================================
// SECTION 4: SETUP FILTER
// ============================================================================

setupFilter = input.string(
     "All",
     title="Trade Setup",
     options=[
         "All",
         "One Only",
         "Two Only",
         "Midway Only"
     ],
     group="Trade Control"
)


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

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


// ============================================================================
// SECTION 6: 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 7: OB STRUCTURE EXIT
// ============================================================================

obStopBufferPoints = input.float(
     0.5,
     title="OB Stop Buffer Points",
     minval=0.0,
     step=0.5,
     group="OB Structure Risk"
)

obRewardRisk = input.float(
     2.0,
     title="OB Reward / Risk",
     minval=0.5,
     maxval=10.0,
     step=0.25,
     group="OB Structure Risk"
)

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


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

breakEvenOffsetPoints = input.float(
     0.5,
     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
// ============================================================================

showLatestOB = input.bool(
     false,
     title="Show Latest Active OB?",
     group="Display"
)

showSourceSignals = input.bool(
     false,
     title="Show Raw First-Touch Signals?",
     group="Display"
)

showEntrySignals = input.bool(
     false,
     title="Show Strategy Entries?",
     group="Display"
)

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

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


// ============================================================================
// SECTION 12: CORE ATR / MIDWAY PROFILE
// ============================================================================

float atrValue = ta.atr(50)

float midwayBosBreakATR = 0.25
float midwayBosBodyATR = 0.60
float midwaySwingReactionATR = 0.50

int midwayFormationBars = 30
int midwayMinimumScore = 4


if midwayQualityMode == "Aggressive"

    midwayBosBreakATR := 0.10

    midwayBosBodyATR := 0.30

    midwaySwingReactionATR := 0.25

    midwayFormationBars := 75

    midwayMinimumScore := 2


else if midwayQualityMode == "Moderate"

    midwayBosBreakATR := 0.15

    midwayBosBodyATR := 0.45

    midwaySwingReactionATR := 0.35

    midwayFormationBars := 50

    midwayMinimumScore := 3


// ============================================================================
// SECTION 13: PIVOT STRUCTURE ARRAYS
// ============================================================================

var array<string> structureTypes = array.new_string()

var array<float> structureValues = array.new_float()

var array<int> structureIndexes = array.new_int()


float highPivot = ta.pivothigh(
     pivotPeriod,
     pivotPeriod
)

float lowPivot = ta.pivotlow(
     pivotPeriod,
     pivotPeriod
)


float highValue = ta.valuewhen(
     not na(highPivot),
     high[pivotPeriod],
     0
)


float lowValue = ta.valuewhen(
     not na(lowPivot),
     low[pivotPeriod],
     0
)


int highIndex = int(
     ta.valuewhen(
         not na(highPivot),
         bar_index[pivotPeriod],
         0
     )
)


int lowIndex = int(
     ta.valuewhen(
         not na(lowPivot),
         bar_index[pivotPeriod],
         0
     )
)


// ============================================================================
// SECTION 14: STRUCTURE CLASSIFICATION
// ============================================================================
//
// This section preserves the supplied indicator's HH / HL / LH / LL state
// construction.
//
// ============================================================================

if not na(highPivot) and not na(lowPivot)

    if array.size(structureTypes) == 0

        array.push(
             structureTypes,
             "H"
        )

        array.push(
             structureValues,
             highValue
        )

        array.push(
             structureIndexes,
             highIndex
        )


    else

        string lastType = array.get(
             structureTypes,
             array.size(structureTypes) - 1
        )

        float lastValue = array.get(
             structureValues,
             array.size(structureValues) - 1
        )


        if lastType == "L" or lastType == "LL"

            if lowPivot < lastValue

                array.remove(
                     structureTypes,
                     array.size(structureTypes) - 1
                )

                array.remove(
                     structureValues,
                     array.size(structureValues) - 1
                )

                array.remove(
                     structureIndexes,
                     array.size(structureIndexes) - 1
                )


                string newLowType = "L"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newLowType := compareValue < lowValue ? "HL" : "LL"


                array.push(
                     structureTypes,
                     newLowType
                )

                array.push(
                     structureValues,
                     lowValue
                )

                array.push(
                     structureIndexes,
                     lowIndex
                )


            else

                string newHighType = "H"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newHighType := compareValue < highValue ? "HH" : "LH"


                array.push(
                     structureTypes,
                     newHighType
                )

                array.push(
                     structureValues,
                     highValue
                )

                array.push(
                     structureIndexes,
                     highIndex
                )


        else if lastType == "H" or lastType == "HH"

            if highPivot > lastValue

                array.remove(
                     structureTypes,
                     array.size(structureTypes) - 1
                )

                array.remove(
                     structureValues,
                     array.size(structureValues) - 1
                )

                array.remove(
                     structureIndexes,
                     array.size(structureIndexes) - 1
                )


                string newHighType = "H"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newHighType := compareValue < highValue ? "HH" : "LH"


                array.push(
                     structureTypes,
                     newHighType
                )

                array.push(
                     structureValues,
                     highValue
                )

                array.push(
                     structureIndexes,
                     highIndex
                )


            else

                string newLowType = "L"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newLowType := compareValue < lowValue ? "HL" : "LL"


                array.push(
                     structureTypes,
                     newLowType
                )

                array.push(
                     structureValues,
                     lowValue
                )

                array.push(
                     structureIndexes,
                     lowIndex
                )


        else if lastType == "LH"

            if highPivot < lastValue

                string newLowType = "L"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newLowType := compareValue < lowValue ? "HL" : "LL"


                array.push(
                     structureTypes,
                     newLowType
                )

                array.push(
                     structureValues,
                     lowValue
                )

                array.push(
                     structureIndexes,
                     lowIndex
                )


            else if highPivot > lastValue

                if close < lastValue

                    array.remove(
                         structureTypes,
                         array.size(structureTypes) - 1
                    )

                    array.remove(
                         structureValues,
                         array.size(structureValues) - 1
                    )

                    array.remove(
                         structureIndexes,
                         array.size(structureIndexes) - 1
                    )


                    string newHighType = "H"


                    if array.size(structureTypes) > 2

                        float compareValue = array.get(
                             structureValues,
                             array.size(structureValues) - 2
                        )

                        newHighType := compareValue < highValue ? "HH" : "LH"


                    array.push(
                         structureTypes,
                         newHighType
                    )

                    array.push(
                         structureValues,
                         highValue
                    )

                    array.push(
                         structureIndexes,
                         highIndex
                    )


                else if close > lastValue

                    string newLowType = "L"


                    if array.size(structureTypes) > 2

                        float compareValue = array.get(
                             structureValues,
                             array.size(structureValues) - 2
                        )

                        newLowType := compareValue < lowValue ? "HL" : "LL"


                    array.push(
                         structureTypes,
                         newLowType
                    )

                    array.push(
                         structureValues,
                         lowValue
                    )

                    array.push(
                         structureIndexes,
                         lowIndex
                    )


        else if lastType == "HL"

            if lowPivot > lastValue

                string newHighType = "H"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newHighType := compareValue < highValue ? "HH" : "LH"


                array.push(
                     structureTypes,
                     newHighType
                )

                array.push(
                     structureValues,
                     highValue
                )

                array.push(
                     structureIndexes,
                     highIndex
                )


            else if lowPivot < lastValue

                if close > lastValue

                    array.remove(
                         structureTypes,
                         array.size(structureTypes) - 1
                    )

                    array.remove(
                         structureValues,
                         array.size(structureValues) - 1
                    )

                    array.remove(
                         structureIndexes,
                         array.size(structureIndexes) - 1
                    )


                    string newLowType = "L"


                    if array.size(structureTypes) > 2

                        float compareValue = array.get(
                             structureValues,
                             array.size(structureValues) - 2
                        )

                        newLowType := compareValue < lowValue ? "HL" : "LL"


                    array.push(
                         structureTypes,
                         newLowType
                    )

                    array.push(
                         structureValues,
                         lowValue
                    )

                    array.push(
                         structureIndexes,
                         lowIndex
                    )


                else if close < lastValue

                    string newHighType = "H"


                    if array.size(structureTypes) > 2

                        float compareValue = array.get(
                             structureValues,
                             array.size(structureValues) - 2
                        )

                        newHighType := compareValue < highValue ? "HH" : "LH"


                    array.push(
                         structureTypes,
                         newHighType
                    )

                    array.push(
                         structureValues,
                         highValue
                    )

                    array.push(
                         structureIndexes,
                         highIndex
                    )


else if not na(highPivot)

    if array.size(structureTypes) == 0

        array.insert(
             structureTypes,
             0,
             "H"
        )

        array.insert(
             structureValues,
             0,
             highValue
        )

        array.insert(
             structureIndexes,
             0,
             highIndex
        )


    else

        string lastType = array.get(
             structureTypes,
             array.size(structureTypes) - 1
        )


        float lastValue = array.get(
             structureValues,
             array.size(structureValues) - 1
        )


        if lastType == "L" or lastType == "HL" or lastType == "LL"

            if highPivot > lastValue

                string newHighType = "H"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newHighType := compareValue < highValue ? "HH" : "LH"


                array.push(
                     structureTypes,
                     newHighType
                )

                array.push(
                     structureValues,
                     highValue
                )

                array.push(
                     structureIndexes,
                     highIndex
                )


        else if lastType == "H" or lastType == "HH" or lastType == "LH"

            if lastValue < highValue

                array.remove(
                     structureTypes,
                     array.size(structureTypes) - 1
                )

                array.remove(
                     structureValues,
                     array.size(structureValues) - 1
                )

                array.remove(
                     structureIndexes,
                     array.size(structureIndexes) - 1
                )


                string newHighType = "H"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newHighType := compareValue < highValue ? "HH" : "LH"


                array.push(
                     structureTypes,
                     newHighType
                )

                array.push(
                     structureValues,
                     highValue
                )

                array.push(
                     structureIndexes,
                     highIndex
                )


else if not na(lowPivot)

    if array.size(structureTypes) == 0

        array.insert(
             structureTypes,
             0,
             "L"
        )

        array.insert(
             structureValues,
             0,
             lowValue
        )

        array.insert(
             structureIndexes,
             0,
             lowIndex
        )


    else

        string lastType = array.get(
             structureTypes,
             array.size(structureTypes) - 1
        )


        float lastValue = array.get(
             structureValues,
             array.size(structureValues) - 1
        )


        if lastType == "H" or lastType == "HH" or lastType == "LH"

            if lowPivot < lastValue

                string newLowType = "L"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newLowType := compareValue < lowValue ? "HL" : "LL"


                array.push(
                     structureTypes,
                     newLowType
                )

                array.push(
                     structureValues,
                     lowValue
                )

                array.push(
                     structureIndexes,
                     lowIndex
                )


        else if lastType == "L" or lastType == "HL" or lastType == "LL"

            if lastValue > lowValue

                array.remove(
                     structureTypes,
                     array.size(structureTypes) - 1
                )

                array.remove(
                     structureValues,
                     array.size(structureValues) - 1
                )

                array.remove(
                     structureIndexes,
                     array.size(structureIndexes) - 1
                )


                string newLowType = "L"


                if array.size(structureTypes) > 2

                    float compareValue = array.get(
                         structureValues,
                         array.size(structureValues) - 2
                    )

                    newLowType := compareValue < lowValue ? "HL" : "LL"


                array.push(
                     structureTypes,
                     newLowType
                )

                array.push(
                     structureValues,
                     lowValue
                )

                array.push(
                     structureIndexes,
                     lowIndex
                )


// ============================================================================
// SECTION 15: READ LATEST STRUCTURE SEQUENCE
// ============================================================================

int structureCount = array.size(structureTypes)

bool enoughFive = structureCount >= 5

bool enoughOriginal = structureCount > 8


string type0 = enoughFive ? array.get(structureTypes, structureCount - 1) : ""

string type1 = enoughFive ? array.get(structureTypes, structureCount - 2) : ""

string type2 = enoughFive ? array.get(structureTypes, structureCount - 3) : ""

string type3 = enoughFive ? array.get(structureTypes, structureCount - 4) : ""

string type4 = enoughFive ? array.get(structureTypes, structureCount - 5) : ""

string type5 = enoughOriginal ? array.get(structureTypes, structureCount - 6) : ""

string type6 = enoughOriginal ? array.get(structureTypes, structureCount - 7) : ""


float price0 = enoughFive ? array.get(structureValues, structureCount - 1) : na

float price1 = enoughFive ? array.get(structureValues, structureCount - 2) : na

float price3 = enoughFive ? array.get(structureValues, structureCount - 4) : na

float price4 = enoughFive ? array.get(structureValues, structureCount - 5) : na


int index0 = enoughFive ? array.get(structureIndexes, structureCount - 1) : na

int index1 = enoughFive ? array.get(structureIndexes, structureCount - 2) : na

int index3 = enoughFive ? array.get(structureIndexes, structureCount - 4) : na

int index5 = enoughOriginal ? array.get(structureIndexes, structureCount - 6) : na


bool confirmedStructurePivot = (
     barstate.isconfirmed and
     enoughFive and
     index0 == bar_index - pivotPeriod
)


// ============================================================================
// SECTION 16: ONE / TWO RAW SETUP PATTERNS
// ============================================================================

bool bullOriginal = (
     confirmedStructurePivot and
     enoughOriginal and
     type0 == "HH" and
     type1 == "HL" and
     type2 == "LH" and
     type3 == "HL" and
     type4 == "HH" and
     type5 == "HL" and
     type6 == "HH" and
     price0 < price4
)


bool bearOriginal = (
     confirmedStructurePivot and
     enoughOriginal and
     type0 == "LL" and
     type1 == "LH" and
     type2 == "HL" and
     type3 == "LH" and
     type4 == "LL" and
     type5 == "LH" and
     type6 == "LL" and
     price0 > price4
)


bool bullShort = (
     confirmedStructurePivot and
     type0 == "HH" and
     type1 == "HL" and
     type2 == "LH" and
     type3 == "HL" and
     type4 == "HH" and
     price0 < price4
)


bool bearShort = (
     confirmedStructurePivot and
     type0 == "LL" and
     type1 == "LH" and
     type2 == "HL" and
     type3 == "LH" and
     type4 == "LL" and
     price0 > price4
)


bool bullOriginalEligible = (
     bullOriginal and
     not na(index5) and
     bar_index - index5 <= orderBlockValidityBars
)


bool bearOriginalEligible = (
     bearOriginal and
     not na(index5) and
     bar_index - index5 <= orderBlockValidityBars
)


bool bullShortEligible = (
     bullShort and
     not na(index3) and
     bar_index - index3 <= orderBlockValidityBars
)


bool bearShortEligible = (
     bearShort and
     not na(index3) and
     bar_index - index3 <= orderBlockValidityBars
)


bool bullRaw = bullOriginalEligible or bullShortEligible

bool bearRaw = bearOriginalEligible or bearShortEligible


var int bullishOBOrigin = 0

var int bearishOBOrigin = 0


if bullRaw
    bullishOBOrigin := bullOriginalEligible ? index5 : index3


if bearRaw
    bearishOBOrigin := bearOriginalEligible ? index5 : index3


// ============================================================================
// SECTION 17: UNTOUCHED LIQUIDITY FUNCTION
// ============================================================================

f_levelUntouched(int direction, float price, int pivotBar) =>

    bool valid = (
         not na(price) and
         not na(pivotBar) and
         pivotBar < bar_index and
         bar_index - pivotBar <= 4998
    )


    if valid

        int elapsed = bar_index - pivotBar


        for offset = 0 to elapsed - 1

            bool consumed = false


            if direction == 1

                consumed := low[offset] <= price


            else

                consumed := high[offset] >= price


            if consumed

                valid := false

                break


    valid


// ============================================================================
// SECTION 18: MIDWAY PIVOTS
// ============================================================================

float midwayHighPivot = ta.pivothigh(
     midwayPivotPeriod,
     midwayPivotPeriod
)

float midwayLowPivot = ta.pivotlow(
     midwayPivotPeriod,
     midwayPivotPeriod
)


float midwayLowReaction = ta.highest(
     high,
     midwayPivotPeriod
) - low[midwayPivotPeriod]


float midwayHighReaction = high[midwayPivotPeriod] - ta.lowest(
     low,
     midwayPivotPeriod
)


var float midwayLastHigh = na

var float midwayLastLow = na

var int midwayLastHighBar = na

var int midwayLastLowBar = na


bool midwayBullBreak = false

bool midwayBearBreak = false

bool midwayBullBosStrong = false

bool midwayBearBosStrong = false

int midwayBullAnchor = na

int midwayBearAnchor = na


float midwayBreakBuffer = math.max(
     syminfo.mintick,
     nz(atrValue) * midwayBosBreakATR
)


// ============================================================================
// SECTION 19: MIDWAY BOS
// ============================================================================

if (
     enableMidwaySetup and
     barstate.isconfirmed and
     not na(atrValue)
)

    if (
         not na(midwayLastHigh) and
         not na(midwayLastLow) and
         midwayLastLowBar > midwayLastHighBar and
         bar_index > midwayLastLowBar
    )

        midwayBullBreak := (
             close > open and
             close - open >= atrValue * midwayBosBodyATR and
             close > midwayLastHigh + midwayBreakBuffer and
             close[1] <= midwayLastHigh + midwayBreakBuffer
        )


        if midwayBullBreak

            midwayBullAnchor := midwayLastLowBar

            midwayBullBosStrong := (
                 close - midwayLastHigh >= atrValue * math.max(0.30, midwayBosBreakATR * 1.5) and
                 close - open >= atrValue * math.max(0.65, midwayBosBodyATR * 1.3)
            )


    if (
         not na(midwayLastHigh) and
         not na(midwayLastLow) and
         midwayLastHighBar > midwayLastLowBar and
         bar_index > midwayLastHighBar
    )

        midwayBearBreak := (
             close < open and
             open - close >= atrValue * midwayBosBodyATR and
             close < midwayLastLow - midwayBreakBuffer and
             close[1] >= midwayLastLow - midwayBreakBuffer
        )


        if midwayBearBreak

            midwayBearAnchor := midwayLastHighBar

            midwayBearBosStrong := (
                 midwayLastLow - close >= atrValue * math.max(0.30, midwayBosBreakATR * 1.5) and
                 open - close >= atrValue * math.max(0.65, midwayBosBodyATR * 1.3)
            )


// ============================================================================
// SECTION 20: UPDATE MIDWAY PIVOTS
// ============================================================================

if barstate.isconfirmed

    if not na(midwayHighPivot)

        midwayLastHigh := high[midwayPivotPeriod]

        midwayLastHighBar := bar_index - midwayPivotPeriod


    if not na(midwayLowPivot)

        midwayLastLow := low[midwayPivotPeriod]

        midwayLastLowBar := bar_index - midwayPivotPeriod


// ============================================================================
// SECTION 21: ORDER BLOCK REFINER
// ============================================================================
//
// External dependency retained from the source indicator.
//
// ============================================================================

[
     bullXd1,
     bullXd2,
     bullYd12,
     bullXp1,
     bullXp2,
     bullYp12
] = Refiner.OBRefiner(
     "Demand",
     refineOrderBlock ? "On" : "Off",
     refineMode,
     bullRaw,
     bullishOBOrigin
)


[
     bearXd1,
     bearXd2,
     bearYd12,
     bearXp1,
     bearXp2,
     bearYp12
] = Refiner.OBRefiner(
     "Supply",
     refineOrderBlock ? "On" : "Off",
     refineMode,
     bearRaw,
     bearishOBOrigin
)


[
     midwayBullXd1,
     midwayBullXd2,
     midwayBullYd12,
     midwayBullXp1,
     midwayBullXp2,
     midwayBullYp12
] = Refiner.OBRefiner(
     "Demand",
     refineOrderBlock ? "On" : "Off",
     refineMode,
     midwayBullBreak,
     nz(midwayBullAnchor, 0)
)


[
     midwayBearXd1,
     midwayBearXd2,
     midwayBearYd12,
     midwayBearXp1,
     midwayBearXp2,
     midwayBearYp12
] = Refiner.OBRefiner(
     "Supply",
     refineOrderBlock ? "On" : "Off",
     refineMode,
     midwayBearBreak,
     nz(midwayBearAnchor, 0)
)


// ============================================================================
// SECTION 22: TYPES
// ============================================================================

type MidwayCandidate

    int direction

    int originBar

    int breakBar

    float top

    float bottom

    bool strongBos

    bool goodOB

    bool cleanApproach

    int oppositeStreak


type OrderBlock

    int direction

    int originBar

    int createdBar

    float top

    float bottom

    bool active

    bool entryMarked

    int setupVersion

    string setupName

    int breakBar


var array<OrderBlock> blocks = array.new<OrderBlock>()

var array<MidwayCandidate> midwayCandidates = array.new<MidwayCandidate>()


// ============================================================================
// SECTION 23: NEW BLOCK HELPER
// ============================================================================

f_newBlock(
     int direction,
     int originBar,
     float top,
     float bottom,
     int setupVersion,
     string setupName,
     int breakBar
) =>

    OrderBlock.new(
         direction,
         originBar,
         bar_index,
         top,
         bottom,
         true,
         false,
         setupVersion,
         setupName,
         breakBar
    )


// ============================================================================
// SECTION 24: BLOCK TRIM
// ============================================================================

while array.size(blocks) > 100

    array.shift(
         blocks
    )


while array.size(midwayCandidates) > 24

    array.shift(
         midwayCandidates
    )


// ============================================================================
// SECTION 25: ONE / TWO SETUP SPACING
// ============================================================================

float minimumLiquidityGap = math.max(
     2.0 * syminfo.mintick,
     nz(atrValue) * 0.2
)


// ============================================================================
// SECTION 26: CREATE BULL ONE / TWO OB
// ============================================================================

bool bullOneCreated = false

bool bullTwoCreated = false


if (
     bullRaw and
     not na(bullXd1) and
     not na(bullYd12) and
     not na(bullYp12)
)

    float blockTop = math.max(
         bullYd12,
         bullYp12
    )

    float blockBottom = math.min(
         bullYd12,
         bullYp12
    )

    int blockOrigin = bullXd1


    if (
         blockTop > blockBottom and
         blockOrigin <= bar_index and
         bar_index - blockOrigin <= orderBlockValidityBars
    )

        bool liquidityA = (
             bullOriginalEligible and
             f_levelUntouched(
                 1,
                 price3,
                 index3
             ) and
             price3 > blockTop
        )


        bool liquidityB = (
             f_levelUntouched(
                 1,
                 price1,
                 index1
             ) and
             price1 > blockTop
        )


        bool twoLiquidity = (
             liquidityA and
             liquidityB and
             math.abs(price3 - price1) >= minimumLiquidityGap
        )


        int liquidityCount = (
             twoLiquidity
             ? 2
             : liquidityA or liquidityB
             ? 1
             : 0
        )


        bool createBlock = (
             (
                 twoLiquidity and
                 enableTwoSetup
             ) or
             (
                 liquidityCount == 1 and
                 enableOneSetup
             )
        )


        if createBlock

            int setupVersion = twoLiquidity ? 2 : 1

            string setupName = twoLiquidity ? "TWO" : "ONE"


            array.push(
                 blocks,
                 f_newBlock(
                     1,
                     blockOrigin,
                     blockTop,
                     blockBottom,
                     setupVersion,
                     setupName,
                     na
                 )
            )


            bullTwoCreated := twoLiquidity

            bullOneCreated := not twoLiquidity


// ============================================================================
// SECTION 27: CREATE BEAR ONE / TWO OB
// ============================================================================

bool bearOneCreated = false

bool bearTwoCreated = false


if (
     bearRaw and
     not na(bearXd1) and
     not na(bearYd12) and
     not na(bearYp12)
)

    float blockTop = math.max(
         bearYd12,
         bearYp12
    )

    float blockBottom = math.min(
         bearYd12,
         bearYp12
    )

    int blockOrigin = bearXd1


    if (
         blockTop > blockBottom and
         blockOrigin <= bar_index and
         bar_index - blockOrigin <= orderBlockValidityBars
    )

        bool liquidityA = (
             bearOriginalEligible and
             f_levelUntouched(
                 -1,
                 price3,
                 index3
             ) and
             price3 < blockBottom
        )


        bool liquidityB = (
             f_levelUntouched(
                 -1,
                 price1,
                 index1
             ) and
             price1 < blockBottom
        )


        bool twoLiquidity = (
             liquidityA and
             liquidityB and
             math.abs(price3 - price1) >= minimumLiquidityGap
        )


        int liquidityCount = (
             twoLiquidity
             ? 2
             : liquidityA or liquidityB
             ? 1
             : 0
        )


        bool createBlock = (
             (
                 twoLiquidity and
                 enableTwoSetup
             ) or
             (
                 liquidityCount == 1 and
                 enableOneSetup
             )
        )


        if createBlock

            int setupVersion = twoLiquidity ? 2 : 1

            string setupName = twoLiquidity ? "TWO" : "ONE"


            array.push(
                 blocks,
                 f_newBlock(
                     -1,
                     blockOrigin,
                     blockTop,
                     blockBottom,
                     setupVersion,
                     setupName,
                     na
                 )
            )


            bearTwoCreated := twoLiquidity

            bearOneCreated := not twoLiquidity


// ============================================================================
// SECTION 28: MIDWAY CANDIDATE CREATION
// ============================================================================

if (
     enableMidwaySetup and
     barstate.isconfirmed and
     midwayBullBreak and
     not na(midwayBullXd1) and
     not na(midwayBullYd12) and
     not na(midwayBullYp12)
)

    float zoneTop = math.max(
         midwayBullYd12,
         midwayBullYp12
    )

    float zoneBottom = math.min(
         midwayBullYd12,
         midwayBullYp12
    )


    if (
         zoneTop > zoneBottom and
         midwayBullXd1 <= bar_index and
         bar_index - midwayBullXd1 < orderBlockValidityBars
    )

        bool duplicate = false


        if array.size(midwayCandidates) > 0

            for candidate in midwayCandidates

                if (
                     candidate.direction == 1 and
                     candidate.originBar == midwayBullXd1
                )

                    duplicate := true


        if array.size(blocks) > 0

            for block in blocks

                if (
                     block.setupVersion == 3 and
                     block.direction == 1 and
                     block.originBar == midwayBullXd1
                )

                    duplicate := true


        if not duplicate

            bool goodOB = (
                 zoneTop - zoneBottom <= atrValue * 1.5 and
                 close - zoneTop >= atrValue * 0.9
            )


            array.push(
                 midwayCandidates,
                 MidwayCandidate.new(
                     1,
                     midwayBullXd1,
                     bar_index,
                     zoneTop,
                     zoneBottom,
                     midwayBullBosStrong,
                     goodOB,
                     true,
                     0
                 )
            )


// ============================================================================
// SECTION 29: BEAR MIDWAY CANDIDATE
// ============================================================================

if (
     enableMidwaySetup and
     barstate.isconfirmed and
     midwayBearBreak and
     not na(midwayBearXd1) and
     not na(midwayBearYd12) and
     not na(midwayBearYp12)
)

    float zoneTop = math.max(
         midwayBearYd12,
         midwayBearYp12
    )

    float zoneBottom = math.min(
         midwayBearYd12,
         midwayBearYp12
    )


    if (
         zoneTop > zoneBottom and
         midwayBearXd1 <= bar_index and
         bar_index - midwayBearXd1 < orderBlockValidityBars
    )

        bool duplicate = false


        if array.size(midwayCandidates) > 0

            for candidate in midwayCandidates

                if (
                     candidate.direction == -1 and
                     candidate.originBar == midwayBearXd1
                )

                    duplicate := true


        if array.size(blocks) > 0

            for block in blocks

                if (
                     block.setupVersion == 3 and
                     block.direction == -1 and
                     block.originBar == midwayBearXd1
                )

                    duplicate := true


        if not duplicate

            bool goodOB = (
                 zoneTop - zoneBottom <= atrValue * 1.5 and
                 zoneBottom - close >= atrValue * 0.9
            )


            array.push(
                 midwayCandidates,
                 MidwayCandidate.new(
                     -1,
                     midwayBearXd1,
                     bar_index,
                     zoneTop,
                     zoneBottom,
                     midwayBearBosStrong,
                     goodOB,
                     true,
                     0
                 )
            )


// ============================================================================
// SECTION 30: PROCESS MIDWAY CANDIDATES
// ============================================================================

if (
     enableMidwaySetup and
     array.size(midwayCandidates) > 0
)

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

        MidwayCandidate candidate = array.get(
             midwayCandidates,
             i
        )


        bool expired = (
             bar_index - candidate.originBar >= orderBlockValidityBars or
             bar_index - candidate.breakBar > midwayFormationBars
        )


        bool tested = false


        if bar_index > candidate.breakBar

            if candidate.direction == 1

                tested := low <= candidate.top


            else

                tested := high >= candidate.bottom


        if expired or tested

            array.remove(
                 midwayCandidates,
                 i
            )


        else if barstate.isconfirmed

            bool oppositeImpulse = false


            if candidate.direction == 1

                oppositeImpulse := (
                     close < open and
                     open - close >= atrValue * 0.8
                )


            else

                oppositeImpulse := (
                     close > open and
                     close - open >= atrValue * 0.8
                )


            candidate.oppositeStreak := (
                 oppositeImpulse
                 ? candidate.oppositeStreak + 1
                 : 0
            )


            if candidate.oppositeStreak >= 2
                candidate.cleanApproach := false


            bool hasPivot = (
                 candidate.direction == 1
                 ? not na(midwayLowPivot)
                 : not na(midwayHighPivot)
            )


            int pivotX = bar_index - midwayPivotPeriod


            float pivotY = (
                 candidate.direction == 1
                 ? low[midwayPivotPeriod]
                 : high[midwayPivotPeriod]
            )


            float reaction = (
                 candidate.direction == 1
                 ? midwayLowReaction
                 : midwayHighReaction
            )


            bool separated = (
                 candidate.direction == 1
                 ? pivotY > candidate.top
                 : pivotY < candidate.bottom
            )


            bool fresh = (
                 hasPivot and
                 pivotX > candidate.breakBar and
                 separated and
                 f_levelUntouched(
                     candidate.direction,
                     pivotY,
                     pivotX
                 )
            )


            if (
                 fresh and
                 not na(reaction) and
                 reaction >= atrValue * midwaySwingReactionATR
            )

                int score = 0


                if candidate.strongBos
                    score += 1


                if reaction >= atrValue * math.max(0.55, midwaySwingReactionATR * 1.5)
                    score += 1


                if candidate.goodOB
                    score += 1


                if candidate.cleanApproach
                    score += 1


                if score >= midwayMinimumScore

                    bool alreadyExists = false


                    if array.size(blocks) > 0

                        for block in blocks

                            if (
                                 block.direction == candidate.direction and
                                 block.originBar == candidate.originBar and
                                 block.active
                            )

                                alreadyExists := true


                    if not alreadyExists

                        array.push(
                             blocks,
                             f_newBlock(
                                 candidate.direction,
                                 candidate.originBar,
                                 candidate.top,
                                 candidate.bottom,
                                 3,
                                 "MIDWAY",
                                 candidate.breakBar
                             )
                        )


                    array.remove(
                         midwayCandidates,
                         i
                    )


            else

                array.set(
                     midwayCandidates,
                     i,
                     candidate
                )


// ============================================================================
// SECTION 31: TRIM ARRAYS
// ============================================================================

while array.size(blocks) > 100
    array.shift(blocks)


while array.size(midwayCandidates) > 24
    array.shift(midwayCandidates)


// ============================================================================
// SECTION 32: SETUP FILTER FUNCTION
// ============================================================================

f_setupAllowed(string setupName) =>

    bool allowed = false


    if setupFilter == "All"

        allowed := true


    else if setupFilter == "One Only"

        allowed := setupName == "ONE"


    else if setupFilter == "Two Only"

        allowed := setupName == "TWO"


    else if setupFilter == "Midway Only"

        allowed := setupName == "MIDWAY"


    allowed


// ============================================================================
// SECTION 33: RAW OB FIRST-TOUCH SIGNAL
// ============================================================================
//
// Source behavior:
//
// firstEntry =
// - Active OB
// - Not expired
// - Not previously marked
// - Current bar after OB creation
// - Candle overlaps OB
//
// IMPORTANT:
//
// The source checks firstEntry BEFORE it checks mitigation.
// We preserve that ordering.
//
// ============================================================================

bool rawBullishOBEntry = false

bool rawBearishOBEntry = false


int selectedBlockIndex = na

int selectedCreatedBar = na

float selectedBlockTop = na

float selectedBlockBottom = na

string selectedSetupName = ""


if array.size(blocks) > 0

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

        OrderBlock block = array.get(
             blocks,
             i
        )


        if block.active

            bool expired = (
                 bar_index - block.originBar >= orderBlockValidityBars
            )


            bool firstEntry = (
                 not expired and
                 not block.entryMarked and
                 bar_index > block.createdBar and
                 low <= block.top and
                 high >= block.bottom
            )


            if firstEntry

                block.entryMarked := true


                bool setupAllowed = f_setupAllowed(
                     block.setupName
                )


                if setupAllowed

                    if block.direction == 1

                        rawBullishOBEntry := true


                    else

                        rawBearishOBEntry := true


                    if (
                         na(selectedCreatedBar) or
                         block.createdBar > selectedCreatedBar
                    )

                        selectedBlockIndex := i

                        selectedCreatedBar := block.createdBar

                        selectedBlockTop := block.top

                        selectedBlockBottom := block.bottom

                        selectedSetupName := block.setupName


            bool invalid = false


            if block.direction == 1

                invalid := close < block.bottom


            else

                invalid := close > block.top


            float mitigationPrice = na


            if mitigationLevel == "50 % OB"

                mitigationPrice := math.avg(
                     block.top,
                     block.bottom
                )


            else if mitigationLevel == "Proximal"

                mitigationPrice := (
                     block.direction == 1
                     ? block.top
                     : block.bottom
                )


            else

                mitigationPrice := (
                     block.direction == 1
                     ? block.bottom
                     : block.top
                )


            bool mitigated = false


            if bar_index > block.createdBar

                if block.direction == 1

                    mitigated := low <= mitigationPrice


                else

                    mitigated := high >= mitigationPrice


            if expired or invalid or mitigated

                block.active := false


            array.set(
                 blocks,
                 i,
                 block
            )


// ============================================================================
// 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: 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 36: 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 37: COOLDOWN
// ============================================================================

var int lastExitBar = na


if positionJustClosed
    lastExitBar := bar_index


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


bool tradeLimitOK = (
     tradesToday < maximumTradesPerDay
)


// ============================================================================
// SECTION 38: STRUCTURE RISK ESTIMATE
// ============================================================================

float prospectiveLongStop = na

float prospectiveShortStop = na


if rawBullishOBEntry and not na(selectedBlockBottom)

    prospectiveLongStop := (
         selectedBlockBottom -
         obStopBufferPoints
    )


if rawBearishOBEntry and not na(selectedBlockTop)

    prospectiveShortStop := (
         selectedBlockTop +
         obStopBufferPoints
    )


float prospectiveLongRisk = na

float prospectiveShortRisk = na


if not na(prospectiveLongStop)

    prospectiveLongRisk := close - prospectiveLongStop


if not na(prospectiveShortStop)

    prospectiveShortRisk := prospectiveShortStop - close


// ============================================================================
// SECTION 39: RISK VALIDATION
// ============================================================================

bool longRiskValid = true

bool shortRiskValid = true


if (
     exitMode == "OB Structure RR" and
     rawBullishOBEntry
)

    longRiskValid := (
         not na(prospectiveLongRisk) and
         prospectiveLongRisk > syminfo.mintick
    )


    if (
         longRiskValid and
         maximumOBRiskPoints > 0 and
         prospectiveLongRisk > maximumOBRiskPoints
    )

        longRiskValid := false


if (
     exitMode == "OB Structure RR" and
     rawBearishOBEntry
)

    shortRiskValid := (
         not na(prospectiveShortRisk) and
         prospectiveShortRisk > syminfo.mintick
    )


    if (
         shortRiskValid and
         maximumOBRiskPoints > 0 and
         prospectiveShortRisk > maximumOBRiskPoints
    )

        shortRiskValid := false


// ============================================================================
// SECTION 40: ENTRY APPROVAL
// ============================================================================

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


bool longCandidate = (
     commonEntryApproval and
     allowLong and
     rawBullishOBEntry and
     longRiskValid
)


bool shortCandidate = (
     commonEntryApproval and
     allowShort and
     rawBearishOBEntry and
     shortRiskValid
)


bool ambiguousSignal = (
     longCandidate and
     shortCandidate
)


bool finalLongSignal = (
     longCandidate and
     not ambiguousSignal
)


bool finalShortSignal = (
     shortCandidate and
     not ambiguousSignal
)


// ============================================================================
// SECTION 41: PENDING ENTRY CONTEXT
// ============================================================================

var float pendingStructureStop = na

var string pendingSetupName = ""

var float pendingOBTop = na

var float pendingOBBottom = na


// ============================================================================
// SECTION 42: LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingStructureStop := prospectiveLongStop

    pendingSetupName := selectedSetupName

    pendingOBTop := selectedBlockTop

    pendingOBBottom := selectedBlockBottom


    string entryAlert = (
         "ENTRY_LONG_CNPS30_" +
         selectedSetupName
    )


    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment=selectedSetupName,
         alert_message=entryAlert
    )


    tradesToday += 1


// ============================================================================
// SECTION 43: SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingStructureStop := prospectiveShortStop

    pendingSetupName := selectedSetupName

    pendingOBTop := selectedBlockTop

    pendingOBBottom := selectedBlockBottom


    string entryAlert = (
         "ENTRY_SHORT_CNPS30_" +
         selectedSetupName
    )


    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment=selectedSetupName,
         alert_message=entryAlert
    )


    tradesToday += 1


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

var string activeSetupName = ""

var float activeOBTop = na

var float activeOBBottom = na


// ============================================================================
// SECTION 45: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeSetupName := pendingSetupName

    activeOBTop := pendingOBTop

    activeOBBottom := pendingOBBottom

    breakEvenActivated := false


    if exitMode == "OB Structure RR"

        float structureRisk = (
             activeEntryPrice -
             pendingStructureStop
        )


        if (
             na(structureRisk) or
             structureRisk <= syminfo.mintick
        )

            structureRisk := fixedStopLossPoints


        activeInitialRisk := structureRisk

        activeLongSL := (
             activeEntryPrice -
             activeInitialRisk
        )

        activeLongTP := (
             activeEntryPrice +
             activeInitialRisk *
             obRewardRisk
        )


    else

        activeInitialRisk := fixedStopLossPoints

        activeLongSL := (
             activeEntryPrice -
             fixedStopLossPoints
        )

        activeLongTP := (
             activeEntryPrice +
             fixedTakeProfitPoints
        )


    activeShortSL := na

    activeShortTP := na


    pendingStructureStop := na

    pendingSetupName := ""

    pendingOBTop := na

    pendingOBBottom := na


// ============================================================================
// SECTION 46: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeSetupName := pendingSetupName

    activeOBTop := pendingOBTop

    activeOBBottom := pendingOBBottom

    breakEvenActivated := false


    if exitMode == "OB Structure RR"

        float structureRisk = (
             pendingStructureStop -
             activeEntryPrice
        )


        if (
             na(structureRisk) or
             structureRisk <= syminfo.mintick
        )

            structureRisk := fixedStopLossPoints


        activeInitialRisk := structureRisk

        activeShortSL := (
             activeEntryPrice +
             activeInitialRisk
        )

        activeShortTP := (
             activeEntryPrice -
             activeInitialRisk *
             obRewardRisk
        )


    else

        activeInitialRisk := fixedStopLossPoints

        activeShortSL := (
             activeEntryPrice +
             fixedStopLossPoints
        )

        activeShortTP := (
             activeEntryPrice -
             fixedTakeProfitPoints
        )


    activeLongSL := na

    activeLongTP := na


    pendingStructureStop := na

    pendingSetupName := ""

    pendingOBTop := na

    pendingOBBottom := na


// ============================================================================
// SECTION 47: LONG BREAK EVEN
// ============================================================================

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

    float triggerPrice = (
         activeEntryPrice +
         activeInitialRisk *
         breakEvenTriggerR
    )


    if high >= triggerPrice

        float newStop = (
             activeEntryPrice +
             breakEvenOffsetPoints
        )


        if (
             na(activeLongSL) or
             newStop > activeLongSL
        )

            activeLongSL := newStop


        breakEvenActivated := true


// ============================================================================
// SECTION 48: SHORT BREAK EVEN
// ============================================================================

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

    float triggerPrice = (
         activeEntryPrice -
         activeInitialRisk *
         breakEvenTriggerR
    )


    if low <= triggerPrice

        float newStop = (
             activeEntryPrice -
             breakEvenOffsetPoints
        )


        if (
             na(activeShortSL) or
             newStop < activeShortSL
        )

            activeShortSL := newStop


        breakEvenActivated := true


// ============================================================================
// SECTION 49: LONG EXIT
// ============================================================================

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

    string lossAlert = (
         breakEvenActivated
         ? "EXIT_LONG_BE_CNPS30"
         : "EXIT_LONG_SL_CNPS30"
    )


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


// ============================================================================
// SECTION 50: SHORT EXIT
// ============================================================================

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

    string lossAlert = (
         breakEvenActivated
         ? "EXIT_SHORT_BE_CNPS30"
         : "EXIT_SHORT_SL_CNPS30"
    )


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


// ============================================================================
// SECTION 51: POSITION CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeInitialRisk := na

    activeLongSL := na

    activeLongTP := na

    activeShortSL := na

    activeShortTP := na

    breakEvenActivated := false

    activeSetupName := ""

    activeOBTop := na

    activeOBBottom := na


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


// ============================================================================
// SECTION 53: LATEST ACTIVE ORDER BLOCK
// ============================================================================

float latestBullTop = na

float latestBullBottom = na

float latestBearTop = na

float latestBearBottom = na

int latestBullCreated = na

int latestBearCreated = na


if array.size(blocks) > 0

    for block in blocks

        if block.active

            if block.direction == 1

                if (
                     na(latestBullCreated) or
                     block.createdBar > latestBullCreated
                )

                    latestBullCreated := block.createdBar

                    latestBullTop := block.top

                    latestBullBottom := block.bottom


            else

                if (
                     na(latestBearCreated) or
                     block.createdBar > latestBearCreated
                )

                    latestBearCreated := block.createdBar

                    latestBearTop := block.top

                    latestBearBottom := block.bottom


// ============================================================================
// SECTION 54: OB DISPLAY
// ============================================================================

plot(
     showLatestOB ? latestBullTop : na,
     title="Latest Demand OB Top",
     color=color.new(color.green, 15),
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showLatestOB ? latestBullBottom : na,
     title="Latest Demand OB Bottom",
     color=color.new(color.green, 55),
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showLatestOB ? latestBearTop : na,
     title="Latest Supply OB Top",
     color=color.new(color.red, 55),
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showLatestOB ? latestBearBottom : na,
     title="Latest Supply OB Bottom",
     color=color.new(color.red, 15),
     linewidth=1,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 55: SL / TP DISPLAY
// ============================================================================

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 56: RAW SOURCE SIGNALS
// ============================================================================

plotshape(
     showSourceSignals and rawBullishOBEntry,
     title="Bullish OB First Touch",
     style=shape.circle,
     location=location.belowbar,
     color=color.lime,
     size=size.tiny,
     text="OB"
)


plotshape(
     showSourceSignals and rawBearishOBEntry,
     title="Bearish OB First Touch",
     style=shape.circle,
     location=location.abovebar,
     color=color.orange,
     size=size.tiny,
     text="OB"
)


// ============================================================================
// SECTION 57: STRATEGY ENTRY SIGNALS
// ============================================================================

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


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


// ============================================================================
// SECTION 58: DIAGNOSTIC COUNTERS
// ============================================================================

var int oneBlockCount = 0

var int twoBlockCount = 0

var int midwayBlockCount = 0

var int bullishTouchCount = 0

var int bearishTouchCount = 0

var int oneEntryCount = 0

var int twoEntryCount = 0

var int midwayEntryCount = 0


if bullOneCreated or bearOneCreated
    oneBlockCount += 1


if bullTwoCreated or bearTwoCreated
    twoBlockCount += 1


if rawBullishOBEntry
    bullishTouchCount += 1


if rawBearishOBEntry
    bearishTouchCount += 1


if finalLongSignal or finalShortSignal

    if selectedSetupName == "ONE"

        oneEntryCount += 1


    else if selectedSetupName == "TWO"

        twoEntryCount += 1


    else if selectedSetupName == "MIDWAY"

        midwayEntryCount += 1


// Count newly created Midway OBs by comparing current blocks on this bar.

if array.size(blocks) > 0

    for block in blocks

        if (
             block.setupVersion == 3 and
             block.createdBar == bar_index
        )

            midwayBlockCount += 1


// ============================================================================
// SECTION 59: DIAGNOSTICS
// ============================================================================

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


if showDiagnostics and barstate.islast

    int activeBlocks = 0


    if array.size(blocks) > 0

        for block in blocks

            if block.active
                activeBlocks += 1


    string positionText = "FLAT"


    if inLong

        positionText := "LONG"


    else if inShort

        positionText := "SHORT"


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


    table.cell(diagnostics, 0, 1, "Structure Points")
    table.cell(diagnostics, 1, 1, str.tostring(structureCount))


    table.cell(diagnostics, 0, 2, "Active OBs")
    table.cell(diagnostics, 1, 2, str.tostring(activeBlocks))


    table.cell(diagnostics, 0, 3, "ONE OBs")
    table.cell(diagnostics, 1, 3, str.tostring(oneBlockCount))


    table.cell(diagnostics, 0, 4, "TWO OBs")
    table.cell(diagnostics, 1, 4, str.tostring(twoBlockCount))


    table.cell(diagnostics, 0, 5, "MIDWAY OBs")
    table.cell(diagnostics, 1, 5, str.tostring(midwayBlockCount))


    table.cell(diagnostics, 0, 6, "Bull Touches")
    table.cell(diagnostics, 1, 6, str.tostring(bullishTouchCount))


    table.cell(diagnostics, 0, 7, "Bear Touches")
    table.cell(diagnostics, 1, 7, str.tostring(bearishTouchCount))


    table.cell(diagnostics, 0, 8, "ONE Entries")
    table.cell(diagnostics, 1, 8, str.tostring(oneEntryCount))


    table.cell(diagnostics, 0, 9, "TWO Entries")
    table.cell(diagnostics, 1, 9, str.tostring(twoEntryCount))


    table.cell(diagnostics, 0, 10, "MIDWAY Entries")
    table.cell(diagnostics, 1, 10, str.tostring(midwayEntryCount))


    table.cell(diagnostics, 0, 11, "Midway Mode")
    table.cell(diagnostics, 1, 11, midwayQualityMode)


    table.cell(diagnostics, 0, 12, "Midway Score")
    table.cell(diagnostics, 1, 12, str.tostring(midwayMinimumScore) + " / 4")


    table.cell(diagnostics, 0, 13, "Setup Filter")
    table.cell(diagnostics, 1, 13, setupFilter)


    table.cell(diagnostics, 0, 14, "Active Setup")
    table.cell(diagnostics, 1, 14, activeSetupName == "" ? "NONE" : activeSetupName)


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


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


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


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