//@version=6

strategy(
     title="CNPS43 ORB SMART TRAIL",
     shorttitle="CNPS43 ORB",
     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=false,
     calc_on_order_fills=true,
     max_labels_count=300,
     max_lines_count=300,
     max_bars_back=5000
)


// ============================================================================
// CNPS43 ORB SMART TRAIL
//
// CORE LOGIC:
//
// 1. Build Opening Range
// 2. Validate Opening Range width
// 3. Enter on breakout
// 4. Initial stop based on OR structure or ATR
// 5. Step-down ATR trailing:
//      < 1R  = 3 ATR
//      1-2R  = 2 ATR
//      >= 2R = 1 ATR
// 6. Combine ATR trail with structural swing trail
// 7. Exit on large reversal candle
// 8. Force close at session end
//
// VN30 DEFAULTS:
//
// Opening Range = 09:00 - 09:15
// Last Entry    = 14:30
// Force Close   = 14:45
// Position      = 3 contracts
// Capital       = 100M VND
// Commission    = 10,000 VND / contract
// Slippage      = 3 ticks
//
// IMPORTANT:
// Source-style next-bar execution is preserved.
// ============================================================================


// ============================================================================
// SECTION 1: OPENING RANGE
// ============================================================================

openingRangeSession = input.session(
     "0900-0915",
     title="Opening Range Window",
     group="Opening Range"
)

maximumOpeningRangeWidthPercent = input.float(
     0.50,
     title="Maximum OR Width (% of Price)",
     minval=0.05,
     maxval=10.0,
     step=0.05,
     group="Opening Range"
)


// ============================================================================
// SECTION 2: ATR RISK
// ============================================================================

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

initialStopATRMultiplier = input.float(
     1.50,
     title="Initial Stop ATR Multiplier",
     minval=0.10,
     step=0.10,
     group="Risk Management"
)


// ============================================================================
// SECTION 3: STEP-DOWN ATR TRAIL
// ============================================================================

trailATRMultiplierBelow1R = input.float(
     3.0,
     title="Trail ATR Multiplier: 0R - 1R",
     minval=0.10,
     step=0.10,
     group="Smart Trailing"
)

trailATRMultiplier1RTo2R = input.float(
     2.0,
     title="Trail ATR Multiplier: 1R - 2R",
     minval=0.10,
     step=0.10,
     group="Smart Trailing"
)

trailATRMultiplierAbove2R = input.float(
     1.0,
     title="Trail ATR Multiplier: Above 2R",
     minval=0.10,
     step=0.10,
     group="Smart Trailing"
)


// ============================================================================
// SECTION 4: STRUCTURAL TRAIL
// ============================================================================

structuralSwingLookback = input.int(
     5,
     title="Structural Swing Lookback",
     minval=1,
     maxval=100,
     group="Smart Trailing"
)


// ============================================================================
// SECTION 5: REVERSAL CANDLE
// ============================================================================

reversalCandleATRMultiplier = input.float(
     1.0,
     title="Reversal Candle Body ATR Multiplier",
     minval=0.10,
     step=0.10,
     group="Smart Trailing"
)


// ============================================================================
// SECTION 6: TRADE DIRECTION
// ============================================================================

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

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

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


// ============================================================================
// SECTION 7: POSITION SIZE
// ============================================================================

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


// ============================================================================
// SECTION 8: SESSION
// ============================================================================

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

lastEntryHour = input.int(
     14,
     title="Last Entry Hour",
     minval=0,
     maxval=23,
     group="Session"
)

lastEntryMinute = input.int(
     30,
     title="Last Entry Minute",
     minval=0,
     maxval=59,
     group="Session"
)

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

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


// ============================================================================
// SECTION 9: ALERTS
// ============================================================================

disableExitAlerts = input.bool(
     false,
     title="Disable Stop / Trail Alerts?",
     group="Alerts"
)


// ============================================================================
// SECTION 10: DISPLAY
// ============================================================================

showOpeningRange = input.bool(
     true,
     title="Show Opening Range?",
     group="Display"
)

showActiveStop = input.bool(
     true,
     title="Show Active Smart Stop?",
     group="Display"
)

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

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


// ============================================================================
// SECTION 11: TIME ENGINE
// ============================================================================

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

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

int lastEntryTotal = lastEntryHour * 60 + lastEntryMinute

int forceCloseTotal = forceCloseHour * 60 + forceCloseMinute

bool entryTimeOK = currentMinute < lastEntryTotal


// ============================================================================
// SECTION 12: TRADING DAY
// ============================================================================

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

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


// ============================================================================
// SECTION 13: OPENING RANGE WINDOW
// ============================================================================

bool inOpeningRange = not na(
     time(
         timeframe.period,
         openingRangeSession,
         strategyTimezone
     )
)


// ============================================================================
// SECTION 14: OPENING RANGE STATE
// ============================================================================

var float openingRangeHigh = na
var float openingRangeLow = na
var bool openingRangeLocked = false

if newTradingDay
    openingRangeHigh := na
    openingRangeLow := na
    openingRangeLocked := false


// ============================================================================
// SECTION 15: BUILD OPENING RANGE
// ============================================================================

if inOpeningRange

    if na(openingRangeHigh)
        openingRangeHigh := high
    else
        openingRangeHigh := math.max(openingRangeHigh, high)

    if na(openingRangeLow)
        openingRangeLow := low
    else
        openingRangeLow := math.min(openingRangeLow, low)


// ============================================================================
// SECTION 16: LOCK OPENING RANGE
// ============================================================================

if (
     not inOpeningRange and
     not openingRangeLocked and
     not na(openingRangeHigh) and
     not na(openingRangeLow)
)
    openingRangeLocked := true


// ============================================================================
// SECTION 17: OPENING RANGE VALIDITY
// ============================================================================

float openingRangeWidthPercent = na

if (
     not na(openingRangeHigh) and
     not na(openingRangeLow) and
     close != 0
)
    openingRangeWidthPercent := (openingRangeHigh - openingRangeLow) / close * 100.0


bool openingRangeValid = (
     openingRangeLocked and
     not na(openingRangeWidthPercent) and
     openingRangeWidthPercent <= maximumOpeningRangeWidthPercent
)


// ============================================================================
// SECTION 18: ATR
// ============================================================================

float atrValue = ta.atr(atrLength)


// ============================================================================
// SECTION 19: DAILY ENTRY STATE
// ============================================================================

var bool longTakenToday = false
var bool shortTakenToday = false

if newTradingDay
    longTakenToday := false
    shortTakenToday := false


// ============================================================================
// SECTION 20: POSITION STATE
// ============================================================================

bool flat = strategy.position_size == 0

bool inLong = strategy.position_size > 0

bool inShort = strategy.position_size < 0

bool positionJustClosed = flat and strategy.position_size[1] != 0


// ============================================================================
// SECTION 21: ENTRY APPROVAL
// ============================================================================

bool canEnter = (
     openingRangeValid and
     entryTimeOK and
     currentCloseMinute < forceCloseTotal and
     flat and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 22: LONG BREAKOUT
// ============================================================================

bool longSignal = (
     allowLong and
     canEnter and
     not longTakenToday and
     close > openingRangeHigh
)


// ============================================================================
// SECTION 23: SHORT BREAKOUT
// ============================================================================

bool shortSignal = (
     allowShort and
     canEnter and
     not shortTakenToday and
     close < openingRangeLow
)


// ============================================================================
// SECTION 24: INITIAL RISK
// ============================================================================

float longOpeningRangeRisk = na
float shortOpeningRangeRisk = na

if not na(openingRangeLow)
    longOpeningRangeRisk := close - openingRangeLow

if not na(openingRangeHigh)
    shortOpeningRangeRisk := openingRangeHigh - close


float minimumATRStopDistance = atrValue * initialStopATRMultiplier

float longInitialRisk = na
float shortInitialRisk = na

if (
     not na(longOpeningRangeRisk) and
     not na(minimumATRStopDistance)
)
    longInitialRisk := math.max(
         longOpeningRangeRisk,
         minimumATRStopDistance
    )

if (
     not na(shortOpeningRangeRisk) and
     not na(minimumATRStopDistance)
)
    shortInitialRisk := math.max(
         shortOpeningRangeRisk,
         minimumATRStopDistance
    )


// ============================================================================
// SECTION 25: ACTIVE TRADE STATE
// ============================================================================

var float sourceEntryPrice = na
var float sourceInitialRisk = na
var float activeStopPrice = na
var int activeEntrySignalBar = na
var float maximumProfitR = 0.0
var float activeTrailMultiplier = na


// ============================================================================
// SECTION 26: LONG ENTRY
// ============================================================================

if (
     longSignal and
     not na(longInitialRisk) and
     longInitialRisk > syminfo.mintick
)

    strategy.entry(
         "Long",
         strategy.long,
         qty=fixedContracts,
         comment="ORB Long",
         alert_message="ENTRY_LONG_CNPS43"
    )

    sourceEntryPrice := close

    sourceInitialRisk := longInitialRisk

    activeStopPrice := close - longInitialRisk

    activeEntrySignalBar := bar_index

    maximumProfitR := 0.0

    activeTrailMultiplier := trailATRMultiplierBelow1R

    longTakenToday := true


// ============================================================================
// SECTION 27: SHORT ENTRY
// ============================================================================

if (
     shortSignal and
     not na(shortInitialRisk) and
     shortInitialRisk > syminfo.mintick
)

    strategy.entry(
         "Short",
         strategy.short,
         qty=fixedContracts,
         comment="ORB Short",
         alert_message="ENTRY_SHORT_CNPS43"
    )

    sourceEntryPrice := close

    sourceInitialRisk := shortInitialRisk

    activeStopPrice := close + shortInitialRisk

    activeEntrySignalBar := bar_index

    maximumProfitR := 0.0

    activeTrailMultiplier := trailATRMultiplierBelow1R

    shortTakenToday := true


// ============================================================================
// SECTION 28: STRUCTURAL SWINGS
// ============================================================================

float structuralSwingHigh = ta.highest(
     high,
     structuralSwingLookback
)

float structuralSwingLow = ta.lowest(
     low,
     structuralSwingLookback
)


// ============================================================================
// SECTION 29: LONG PROFIT IN R
// ============================================================================

float longProfitR = na

if (
     inLong and
     not na(sourceEntryPrice) and
     not na(sourceInitialRisk) and
     sourceInitialRisk > 0
)
    longProfitR := (high - sourceEntryPrice) / sourceInitialRisk


// ============================================================================
// SECTION 30: SHORT PROFIT IN R
// ============================================================================

float shortProfitR = na

if (
     inShort and
     not na(sourceEntryPrice) and
     not na(sourceInitialRisk) and
     sourceInitialRisk > 0
)
    shortProfitR := (sourceEntryPrice - low) / sourceInitialRisk


// ============================================================================
// SECTION 31: LONG SMART TRAIL
// ============================================================================

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

    maximumProfitR := math.max(
         maximumProfitR,
         longProfitR
    )

    float selectedTrailMultiplier = trailATRMultiplierBelow1R

    if maximumProfitR >= 2.0
        selectedTrailMultiplier := trailATRMultiplierAbove2R

    else if maximumProfitR >= 1.0
        selectedTrailMultiplier := trailATRMultiplier1RTo2R

    activeTrailMultiplier := selectedTrailMultiplier

    float atrTrailingStop = high - atrValue * selectedTrailMultiplier

    float structuralTrailingStop = structuralSwingLow

    float newLongStop = math.max(
         atrTrailingStop,
         structuralTrailingStop
    )

    activeStopPrice := math.max(
         activeStopPrice,
         newLongStop
    )

    strategy.exit(
         "Long Smart Exit",
         from_entry="Long",
         stop=activeStopPrice,
         alert_message="SMART_TRAIL_EXIT_LONG_CNPS43",
         disable_alert=disableExitAlerts
    )


// ============================================================================
// SECTION 32: SHORT SMART TRAIL
// ============================================================================

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

    maximumProfitR := math.max(
         maximumProfitR,
         shortProfitR
    )

    float selectedTrailMultiplier = trailATRMultiplierBelow1R

    if maximumProfitR >= 2.0
        selectedTrailMultiplier := trailATRMultiplierAbove2R

    else if maximumProfitR >= 1.0
        selectedTrailMultiplier := trailATRMultiplier1RTo2R

    activeTrailMultiplier := selectedTrailMultiplier

    float atrTrailingStop = low + atrValue * selectedTrailMultiplier

    float structuralTrailingStop = structuralSwingHigh

    float newShortStop = math.min(
         atrTrailingStop,
         structuralTrailingStop
    )

    activeStopPrice := math.min(
         activeStopPrice,
         newShortStop
    )

    strategy.exit(
         "Short Smart Exit",
         from_entry="Short",
         stop=activeStopPrice,
         alert_message="SMART_TRAIL_EXIT_SHORT_CNPS43",
         disable_alert=disableExitAlerts
    )


// ============================================================================
// SECTION 33: REVERSAL CANDLE
// ============================================================================

float candleBody = math.abs(close - open)

bool largeBearishReversalCandle = (
     close < open and
     candleBody > atrValue * reversalCandleATRMultiplier
)

bool largeBullishReversalCandle = (
     close > open and
     candleBody > atrValue * reversalCandleATRMultiplier
)


// ============================================================================
// SECTION 34: LONG REVERSAL EXIT
// ============================================================================

if (
     inLong and
     largeBearishReversalCandle and
     barstate.isconfirmed
)

    strategy.close(
         "Long",
         comment="Reversal Candle",
         alert_message="REVERSAL_EXIT_LONG_CNPS43"
    )


// ============================================================================
// SECTION 35: SHORT REVERSAL EXIT
// ============================================================================

if (
     inShort and
     largeBullishReversalCandle and
     barstate.isconfirmed
)

    strategy.close(
         "Short",
         comment="Reversal Candle",
         alert_message="REVERSAL_EXIT_SHORT_CNPS43"
    )


// ============================================================================
// SECTION 36: FORCE CLOSE
// ============================================================================

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

if (
     forceCloseBar and
     strategy.position_size != 0
)

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


// ============================================================================
// SECTION 37: CLEANUP
// ============================================================================

if positionJustClosed

    sourceEntryPrice := na

    sourceInitialRisk := na

    activeStopPrice := na

    activeEntrySignalBar := na

    maximumProfitR := 0.0

    activeTrailMultiplier := na


// ============================================================================
// SECTION 38: OR HIGH
// ============================================================================

plot(
     showOpeningRange ? openingRangeHigh : na,
     title="OR High",
     color=color.blue,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 39: OR LOW
// ============================================================================

plot(
     showOpeningRange ? openingRangeLow : na,
     title="OR Low",
     color=color.blue,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 40: ACTIVE STOP
// ============================================================================

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


// ============================================================================
// SECTION 41: LONG SIGNAL
// ============================================================================

plotshape(
     showSignalMarkers and longSignal,
     title="ORB Long Signal",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.small,
     text="ORB L"
)


// ============================================================================
// SECTION 42: SHORT SIGNAL
// ============================================================================

plotshape(
     showSignalMarkers and shortSignal,
     title="ORB Short Signal",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.small,
     text="ORB S"
)


// ============================================================================
// SECTION 43: DATA WINDOW
// ============================================================================

plot(
     openingRangeWidthPercent,
     title="OR Width %",
     display=display.data_window
)

plot(
     sourceInitialRisk,
     title="Initial Risk Points",
     display=display.data_window
)

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

plot(
     activeTrailMultiplier,
     title="Active ATR Trail Multiplier",
     display=display.data_window
)


// ============================================================================
// SECTION 44: COUNTERS
// ============================================================================

var int longSignalCount = 0
var int shortSignalCount = 0
var int reversalLongExitCount = 0
var int reversalShortExitCount = 0

if longSignal
    longSignalCount += 1

if shortSignal
    shortSignalCount += 1

if (
     inLong and
     largeBearishReversalCandle and
     barstate.isconfirmed
)
    reversalLongExitCount += 1

if (
     inShort and
     largeBullishReversalCandle and
     barstate.isconfirmed
)
    reversalShortExitCount += 1


// ============================================================================
// SECTION 45: DIAGNOSTICS
// ============================================================================

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

if (
     showDiagnostics and
     barstate.islast
)

    string positionText = "FLAT"

    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


    string rangeState = "BUILDING"

    if openingRangeLocked

        if openingRangeValid
            rangeState := "VALID"

        else
            rangeState := "TOO WIDE"


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

    table.cell(diagnostics, 0, 1, "OR State")
    table.cell(diagnostics, 1, 1, rangeState)

    table.cell(diagnostics, 0, 2, "OR High")
    table.cell(diagnostics, 1, 2, str.tostring(openingRangeHigh, format.mintick))

    table.cell(diagnostics, 0, 3, "OR Low")
    table.cell(diagnostics, 1, 3, str.tostring(openingRangeLow, format.mintick))

    table.cell(diagnostics, 0, 4, "OR Width %")
    table.cell(diagnostics, 1, 4, str.tostring(openingRangeWidthPercent, "0.00"))

    table.cell(diagnostics, 0, 5, "ATR")
    table.cell(diagnostics, 1, 5, str.tostring(atrValue, "0.00"))

    table.cell(diagnostics, 0, 6, "Initial Risk")
    table.cell(diagnostics, 1, 6, str.tostring(sourceInitialRisk, "0.00"))

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

    table.cell(diagnostics, 0, 8, "Trail ATR Mult")
    table.cell(diagnostics, 1, 8, str.tostring(activeTrailMultiplier, "0.00"))

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

    table.cell(diagnostics, 0, 10, "Long Used")
    table.cell(diagnostics, 1, 10, longTakenToday ? "YES" : "NO")

    table.cell(diagnostics, 0, 11, "Short Used")
    table.cell(diagnostics, 1, 11, shortTakenToday ? "YES" : "NO")

    table.cell(diagnostics, 0, 12, "Long Signals")
    table.cell(diagnostics, 1, 12, str.tostring(longSignalCount))

    table.cell(diagnostics, 0, 13, "Short Signals")
    table.cell(diagnostics, 1, 13, str.tostring(shortSignalCount))

    table.cell(diagnostics, 0, 14, "Reversal Exits")
    table.cell(
         diagnostics,
         1,
         14,
         str.tostring(reversalLongExitCount) + " / " + str.tostring(reversalShortExitCount)
    )

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