//@version=6
strategy(
     title="CNPS16 RANGE-BREAKOUT",
     shorttitle="CNPS16 RANGE-BREAKOUT",
     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_boxes_count=100,
     max_bars_back=5000
)


// ╔══════════════════════════════════════════════════════════════════════╗
// ║                   CNPS16 RANGE-BREAKOUT                            ║
// ║             RANGE -> BREAKOUT -> FIXED SL / TP                    ║
// ╠══════════════════════════════════════════════════════════════════════╣
// ║ TIMEFRAME       : CURRENT CHART TIMEFRAME                          ║
// ║ POSITION SIZE   : 3 CONTRACTS                                      ║
// ║ COMMISSION      : VND 10,000 / CONTRACT                           ║
// ║ SLIPPAGE        : 3 TICKS                                          ║
// ║                                                                    ║
// ║ RANGE                                                              ║
// ║ Default: 09:00 -> 09:15                                            ║
// ║                                                                    ║
// ║ LONG                                                               ║
// ║ Build Range                                                        ║
// ║ -> Buy Stop at Range High                                          ║
// ║ -> Breakout                                                        ║
// ║ -> LONG                                                            ║
// ║ -> SL 5 Points                                                     ║
// ║ -> TP 15 Points                                                    ║
// ║                                                                    ║
// ║ SHORT                                                              ║
// ║ Build Range                                                        ║
// ║ -> Sell Stop at Range Low                                          ║
// ║ -> Breakout                                                        ║
// ║ -> SHORT                                                           ║
// ║ -> SL 5 Points                                                     ║
// ║ -> TP 15 Points                                                    ║
// ║                                                                    ║
// ║ ENTRY ORDERS USE OCA: FIRST FILL CANCELS THE OTHER SIDE.          ║
// ║ MAXIMUM ONE TRADE PER DAY.                                         ║
// ╚══════════════════════════════════════════════════════════════════════╝


// ============================================================================
// SECTION 1: RANGE SETTINGS
// ============================================================================

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

rangeStartHour = input.int(
     9,
     title="Range Start Hour",
     minval=0,
     maxval=23,
     group="Range"
)

rangeStartMinute = input.int(
     0,
     title="Range Start Minute",
     minval=0,
     maxval=59,
     group="Range"
)

rangeEndHour = input.int(
     9,
     title="Range End Hour",
     minval=0,
     maxval=23,
     group="Range"
)

rangeEndMinute = input.int(
     15,
     title="Range End Minute",
     minval=0,
     maxval=59,
     group="Range"
)

minimumRangeWidth = input.float(
     0.5,
     title="Minimum Range Width (Points)",
     minval=0.0,
     step=0.5,
     group="Range"
)


// ============================================================================
// SECTION 2: RISK MANAGEMENT
// ============================================================================

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

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


// ============================================================================
// SECTION 3: TRADE DIRECTION
// ============================================================================

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

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

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


// ============================================================================
// SECTION 4: SESSION
// ============================================================================

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

sessionStartMinute = input.int(
     0,
     title="Session Start Minute",
     minval=0,
     maxval=59,
     group="Session"
)

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

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


// ============================================================================
// SECTION 5: ALERT CONTROL
// ============================================================================

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


// ============================================================================
// SECTION 6: DISPLAY
// ============================================================================

showRange = input.bool(
     true,
     title="Show Range High / Low?",
     group="Display"
)

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

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

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


// ============================================================================
// SECTION 7: TIME ENGINE
// ============================================================================
//
// IMPORTANT:
//
// No timeframe lock.
//
// The strategy uses the CURRENT chart timeframe.
//
// ============================================================================

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

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

int rangeStartTotal = (
     rangeStartHour * 60 +
     rangeStartMinute
)

int rangeEndTotal = (
     rangeEndHour * 60 +
     rangeEndMinute
)

int sessionStartTotal = (
     sessionStartHour * 60 +
     sessionStartMinute
)

int sessionEndTotal = (
     sessionEndHour * 60 +
     sessionEndMinute
)

bool validTimeConfiguration = (
     sessionStartTotal <= rangeStartTotal and
     rangeStartTotal < rangeEndTotal and
     rangeEndTotal < sessionEndTotal
)

bool inSession = (
     currentOpenMinute >= sessionStartTotal and
     currentOpenMinute < sessionEndTotal
)

bool insideRangeWindow = (
     validTimeConfiguration and
     currentOpenMinute >= rangeStartTotal and
     currentOpenMinute < rangeEndTotal
)

bool rangeWindowHasClosed = (
     validTimeConfiguration and
     currentCloseMinute >= rangeEndTotal
)

bool beforeSessionEnd = (
     currentCloseMinute < sessionEndTotal
)


// ============================================================================
// SECTION 8: TRADING DAY
// ============================================================================

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

var int activeTradingDayKey = na

bool newTradingDay = (
     inSession and
     (
         na(activeTradingDayKey) or
         tradingDayKey != activeTradingDayKey
     )
)

if newTradingDay

    activeTradingDayKey := (
         tradingDayKey
    )


// ============================================================================
// SECTION 9: DAILY RANGE
// ============================================================================

var float rangeHigh = na
var float rangeLow = na

var bool rangeLocked = false


if newTradingDay

    rangeHigh := na
    rangeLow := na

    rangeLocked := false


if insideRangeWindow

    if na(rangeHigh)

        rangeHigh := (
             high
        )

    else

        rangeHigh := (
             math.max(
                 rangeHigh,
                 high
             )
        )


    if na(rangeLow)

        rangeLow := (
             low
        )

    else

        rangeLow := (
             math.min(
                 rangeLow,
                 low
             )
        )


if (
     not rangeLocked and
     rangeWindowHasClosed and
     not na(rangeHigh) and
     not na(rangeLow)
)

    rangeLocked := true


float rangeWidth = (
     not na(rangeHigh) and
     not na(rangeLow)
     ? rangeHigh -
       rangeLow
     : na
)

bool validRange = (
     rangeLocked and
     not na(rangeWidth) and
     rangeWidth >= minimumRangeWidth
)


// ============================================================================
// SECTION 10: ONE TRADE PER DAY
// ============================================================================

var int tradedDayKey = na

bool tradedToday = (
     not na(tradedDayKey) and
     tradedDayKey == tradingDayKey
)


// ============================================================================
// SECTION 11: POSITION STATE
// ============================================================================

bool inPosition = (
     strategy.position_size != 0
)

bool positionLong = (
     strategy.position_size > 0
)

bool positionShort = (
     strategy.position_size < 0
)

bool positionJustOpened = (
     strategy.position_size != 0 and
     strategy.position_size[1] == 0
)

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


// ============================================================================
// SECTION 12: SAME-BAR TRADE DETECTION
// ============================================================================
//
// Historical stop order can theoretically fill and exit inside the
// same candle.
//
// This ensures that such a trade still consumes the daily setup.
//
// ============================================================================

bool newClosedTrade = (
     strategy.closedtrades >
     nz(
         strategy.closedtrades[1],
         0
     )
)

if newClosedTrade

    tradedDayKey := (
         tradingDayKey
    )


// ============================================================================
// SECTION 13: ENTRY AVAILABILITY
// ============================================================================

bool canArmBreakout = (
     validTimeConfiguration and
     validRange and
     rangeWindowHasClosed and
     beforeSessionEnd and
     inSession and
     not inPosition and
     not tradedToday
)


// ============================================================================
// SECTION 14: LONG ENTRY
// ============================================================================
//
// LONG:
//
// Range completes
// -> Place BUY STOP at Range High
// -> Price trades through Range High
// -> LONG
//
// ============================================================================

bool longSetupArmed = (
     canArmBreakout and
     allowLong
)

if longSetupArmed

    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         stop=rangeHigh,
         oca_name="CNPS16_BREAKOUT",
         oca_type=strategy.oca.cancel,
         comment="Long Breakout",
         alert_message="ENTRY_LONG_CNPS16_RANGE_BREAKOUT"
    )


// ============================================================================
// SECTION 15: SHORT ENTRY
// ============================================================================
//
// SHORT:
//
// Range completes
// -> Place SELL STOP at Range Low
// -> Price trades through Range Low
// -> SHORT
//
// ============================================================================

bool shortSetupArmed = (
     canArmBreakout and
     allowShort
)

if shortSetupArmed

    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         stop=rangeLow,
         oca_name="CNPS16_BREAKOUT",
         oca_type=strategy.oca.cancel,
         comment="Short Breakout",
         alert_message="ENTRY_SHORT_CNPS16_RANGE_BREAKOUT"
    )


// ============================================================================
// SECTION 16: REAL ENTRY CONFIRMATION
// ============================================================================

if positionJustOpened

    tradedDayKey := (
         tradingDayKey
    )

    strategy.cancel(
         "Long"
    )

    strategy.cancel(
         "Short"
    )


// ============================================================================
// SECTION 17: LONG STOP LOSS / TAKE PROFIT
// ============================================================================

var float activeLongSL = na
var float activeLongTP = na

var float activeShortSL = na
var float activeShortTP = na


if positionLong

    float entryPrice = (
         strategy.position_avg_price
    )

    activeLongSL := (
         entryPrice -
         stopLossPoints
    )

    activeLongTP := (
         entryPrice +
         takeProfitPoints
    )


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


// ============================================================================
// SECTION 18: SHORT STOP LOSS / TAKE PROFIT
// ============================================================================

if positionShort

    float entryPrice = (
         strategy.position_avg_price
    )

    activeShortSL := (
         entryPrice +
         stopLossPoints
    )

    activeShortTP := (
         entryPrice -
         takeProfitPoints
    )


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


// ============================================================================
// SECTION 19: POSITION CLEANUP
// ============================================================================

if positionJustClosed

    activeLongSL := na
    activeLongTP := na

    activeShortSL := na
    activeShortTP := na


// ============================================================================
// SECTION 20: CANCEL UNUSED BREAKOUT ORDERS
// ============================================================================

if (
     not canArmBreakout and
     not inPosition
)

    strategy.cancel(
         "Long"
    )

    strategy.cancel(
         "Short"
    )


// ============================================================================
// SECTION 21: SESSION END
// ============================================================================

bool finalSessionBar = (
     inSession and
     currentCloseMinute >= sessionEndTotal
)

if finalSessionBar

    strategy.cancel(
         "Long"
    )

    strategy.cancel(
         "Short"
    )

    if inPosition

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


// ============================================================================
// SECTION 22: RANGE DISPLAY
// ============================================================================

plot(
     showRange and
     not na(rangeHigh)
     ? rangeHigh
     : na,
     title="Range High",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)

plot(
     showRange and
     not na(rangeLow)
     ? rangeLow
     : na,
     title="Range Low",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 23: ACTIVE SL / TP DISPLAY
// ============================================================================

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

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

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

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


// ============================================================================
// SECTION 24: OPTIONAL SIGNAL MARKERS
// ============================================================================
//
// These markers show ARMED SETUPS only.
// They are NOT actual TradingView fills.
//
// ============================================================================

plotshape(
     showSignalMarkers and
     longSetupArmed,
     title="Long Breakout Armed",
     style=shape.triangleup,
     location=location.belowbar,
     size=size.tiny,
     color=color.green,
     text="L",
     textcolor=color.white
)

plotshape(
     showSignalMarkers and
     shortSetupArmed,
     title="Short Breakout Armed",
     style=shape.triangledown,
     location=location.abovebar,
     size=size.tiny,
     color=color.red,
     text="S",
     textcolor=color.white
)


// ============================================================================
// SECTION 25: DIAGNOSTICS
// ============================================================================

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

if (
     showDiagnostics and
     barstate.islast
)

    string statusText = "IDLE"

    if insideRangeWindow
        statusText := "BUILD RANGE"

    if validRange and not tradedToday
        statusText := "WAIT BREAKOUT"

    if tradedToday
        statusText := "DONE TODAY"

    if inPosition
        statusText := positionLong ? "LONG" : "SHORT"


    table.cell(
         diagnostics,
         0,
         0,
         "CNPS16"
    )

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


    table.cell(
         diagnostics,
         0,
         1,
         "Status"
    )

    table.cell(
         diagnostics,
         1,
         1,
         statusText
    )


    table.cell(
         diagnostics,
         0,
         2,
         "Range Locked"
    )

    table.cell(
         diagnostics,
         1,
         2,
         rangeLocked
         ? "YES"
         : "NO"
    )


    table.cell(
         diagnostics,
         0,
         3,
         "Range High"
    )

    table.cell(
         diagnostics,
         1,
         3,
         str.tostring(
             rangeHigh
         )
    )


    table.cell(
         diagnostics,
         0,
         4,
         "Range Low"
    )

    table.cell(
         diagnostics,
         1,
         4,
         str.tostring(
             rangeLow
         )
    )


    table.cell(
         diagnostics,
         0,
         5,
         "Range Width"
    )

    table.cell(
         diagnostics,
         1,
         5,
         str.tostring(
             rangeWidth
         )
    )


    table.cell(
         diagnostics,
         0,
         6,
         "Long Armed"
    )

    table.cell(
         diagnostics,
         1,
         6,
         longSetupArmed
         ? "YES"
         : "NO"
    )


    table.cell(
         diagnostics,
         0,
         7,
         "Short Armed"
    )

    table.cell(
         diagnostics,
         1,
         7,
         shortSetupArmed
         ? "YES"
         : "NO"
    )


    table.cell(
         diagnostics,
         0,
         8,
         "SL / TP"
    )

    table.cell(
         diagnostics,
         1,
         8,
         str.tostring(
             stopLossPoints
         ) +
         " / " +
         str.tostring(
             takeProfitPoints
         )
    )


    table.cell(
         diagnostics,
         0,
         9,
         "Position"
    )

    table.cell(
         diagnostics,
         1,
         9,
         positionLong
         ? "LONG"
         : (
             positionShort
             ? "SHORT"
             : "FLAT"
         )
    )


    table.cell(
         diagnostics,
         0,
         10,
         "Traded Today"
    )

    table.cell(
         diagnostics,
         1,
         10,
         tradedToday
         ? "YES"
         : "NO"
    )