//@version=6
strategy(
     title="CNPS27 SSL MOMENTUM REVERSAL MATRIX",
     shorttitle="CNPS27 SSL MATRIX",
     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
)


// ============================================================================
// CNPS27 SSL MOMENTUM REVERSAL MATRIX
//
// SOURCE:
// WiselyWealth ; SSL Momentum Reversal & Breakout Matrix
//
// ENTRY ENGINE:
// SSL FLIP
//   +
// RSI DIRECTION
//   +
// DMI / ADX CONFIRMATION
//   +
// SSL CHANNEL EXPANSION
//   +
// OPTIONAL HTF SSL ALIGNMENT
//   +
// COMPOSITE SCORE >= PROFILE THRESHOLD
//
// STRATEGY WRAPPER:
// - 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 SL: 5 points
// - Default TP: 15 points
// ============================================================================


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

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

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

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

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


// ============================================================================
// SECTION 2: SSL PARAMETERS
// ============================================================================

maType = input.string(
     "EMA",
     title="MA Type",
     options=["SMA", "EMA", "WMA", "HMA", "RMA", "VWMA"],
     group="SSL Parameters"
)

sslLength = input.int(
     10,
     title="SSL Length",
     minval=1,
     group="SSL Parameters"
)


// ============================================================================
// SECTION 3: SIGNAL PROFILE
// ============================================================================

signalProfile = input.string(
     "Balanced",
     title="Signal Profile",
     options=["Responsive", "Balanced", "Selective"],
     group="Signal Filters"
)


// ============================================================================
// SECTION 4: HTF CONFIRMATION
// ============================================================================

useHTF = input.bool(
     false,
     title="Enable HTF Confirmation",
     group="HTF Confirmation"
)

htfTimeframe = input.timeframe(
     "60",
     title="HTF Timeframe",
     group="HTF Confirmation"
)


// ============================================================================
// SECTION 5: RISK MANAGEMENT
// ============================================================================

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

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


// ============================================================================
// SECTION 6: OPTIONAL BREAK EVEN
// ============================================================================

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

breakEvenTriggerPoints = input.float(
     7.5,
     title="Break Even Trigger Points",
     minval=0.5,
     step=0.5,
     group="Break Even"
)

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


// ============================================================================
// SECTION 7: OPTIONAL SIGNAL EXITS
// ============================================================================

exitOnOppositeQualifiedSignal = input.bool(
     false,
     title="Exit On Opposite Qualified Signal?",
     group="Signal Exit"
)

exitOnTrendWeakening = input.bool(
     false,
     title="Exit On Trend Weakening?",
     group="Signal Exit"
)


// ============================================================================
// SECTION 8: 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 9: ALERTS
// ============================================================================

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


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

showSSL = input.bool(
     true,
     title="Show SSL Channel?",
     group="Display"
)

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

showScoreLabels = input.bool(
     false,
     title="Show Score Labels?",
     group="Display"
)

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

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


// ============================================================================
// SECTION 11: HELPER - MOVING AVERAGE
// ============================================================================

f_ma(float src, int len, string ma_type) =>

    float result = na

    if ma_type == "SMA"
        result := ta.sma(src, len)

    else if ma_type == "EMA"
        result := ta.ema(src, len)

    else if ma_type == "WMA"
        result := ta.wma(src, len)

    else if ma_type == "HMA"
        result := ta.hma(src, len)

    else if ma_type == "RMA"
        result := ta.rma(src, len)

    else if ma_type == "VWMA"
        result := ta.vwma(src, len)

    else
        result := ta.ema(src, len)

    result


// ============================================================================
// SECTION 12: HELPER - CLAMP
// ============================================================================

f_clamp(float value, float minimum, float maximum) =>

    math.min(
         math.max(
             value,
             minimum
         ),
         maximum
    )


// ============================================================================
// SECTION 13: HELPER - SCALE 0 TO 1
// ============================================================================

f_scale01(float value, float minimum, float maximum) =>

    float result = 0.0

    if maximum > minimum

        result := f_clamp(
             (value - minimum) / (maximum - minimum),
             0.0,
             1.0
        )

    result


// ============================================================================
// SECTION 14: HELPER - GRADE
// ============================================================================

f_grade(int score) =>

    string grade = "D"

    if score >= 90
        grade := "A+"

    else if score >= 80
        grade := "A"

    else if score >= 70
        grade := "B"

    else if score >= 60
        grade := "C"

    grade


// ============================================================================
// SECTION 15: SIGNAL PROFILE THRESHOLDS
// ============================================================================

float adxThreshold = 20.0

float widthThreshold = 0.20

int minimumScoreThreshold = 70


if signalProfile == "Responsive"

    adxThreshold := 18.0

    widthThreshold := 0.14

    minimumScoreThreshold := 65


else if signalProfile == "Selective"

    adxThreshold := 25.0

    widthThreshold := 0.28

    minimumScoreThreshold := 80


// ============================================================================
// SECTION 16: 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 17: 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 18: SSL CORE
// ============================================================================

float highMA = f_ma(
     high,
     sslLength,
     maType
)

float lowMA = f_ma(
     low,
     sslLength,
     maType
)


// State:
// 1  = Bullish
// -1 = Bearish

var int sslState = 1


if close > highMA

    sslState := 1


else if close < lowMA

    sslState := -1


else

    sslState := nz(
         sslState[1],
         1
    )


float sslLine = sslState == 1 ? lowMA : highMA


bool flipUp = (
     sslState == 1 and
     sslState[1] == -1
)

bool flipDown = (
     sslState == -1 and
     sslState[1] == 1
)


// ============================================================================
// SECTION 19: RSI FILTER
// ============================================================================

float rsiValue = ta.rsi(
     close,
     14
)

bool buyRSI = rsiValue > 50.0

bool sellRSI = rsiValue < 50.0


// ============================================================================
// SECTION 20: DMI / ADX FILTER
// ============================================================================

[diPlus, diMinus, adxValue] = ta.dmi(
     14,
     14
)

bool buyADX = (
     diPlus > diMinus and
     adxValue > adxThreshold
)

bool sellADX = (
     diMinus > diPlus and
     adxValue > adxThreshold
)


// ============================================================================
// SECTION 21: CHANNEL EXPANSION
// ============================================================================

float atrValue = ta.atr(
     14
)

float channelWidth = math.abs(
     highMA - lowMA
)

float normalizedWidth = 0.0

if atrValue > 0
    normalizedWidth := channelWidth / atrValue


bool channelExpansionOK = (
     normalizedWidth >= widthThreshold
)


// ============================================================================
// SECTION 22: HTF SSL FUNCTION
// ============================================================================

f_htfSSLState(int length, string averageType) =>

    float hMA = f_ma(
         high,
         length,
         averageType
    )

    float lMA = f_ma(
         low,
         length,
         averageType
    )

    var int state = 1


    if close > hMA

        state := 1


    else if close < lMA

        state := -1


    else

        state := nz(
             state[1],
             1
        )


    state


// ============================================================================
// SECTION 23: HTF CONFIRMATION
// ============================================================================

bool htfValid = timeframe.in_seconds(htfTimeframe) > timeframe.in_seconds()


int htfState = request.security(
     syminfo.tickerid,
     htfTimeframe,
     f_htfSSLState(
         sslLength,
         maType
     )[1],
     gaps=barmerge.gaps_off,
     lookahead=barmerge.lookahead_off
)


bool buyHTFConfirmed = (
     not useHTF or
     (
         htfValid and
         htfState == 1
     )
)


bool sellHTFConfirmed = (
     not useHTF or
     (
         htfValid and
         htfState == -1
     )
)


// ============================================================================
// SECTION 24: CANDLE METRICS
// ============================================================================

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

float candleBody = math.abs(
     close - open
)

float bodyRatio = candleBody / candleRange


// ============================================================================
// SECTION 25: PRICE ACTION SCORE
// ============================================================================

float buyCloseLocation = f_clamp(
     (close - low) / candleRange,
     0.0,
     1.0
)

float sellCloseLocation = f_clamp(
     (high - close) / candleRange,
     0.0,
     1.0
)


float buyBreakoutDistance = 0.0

float sellBreakoutDistance = 0.0


if atrValue > 0

    buyBreakoutDistance := math.max(
         close - highMA,
         0.0
    ) / atrValue

    sellBreakoutDistance := math.max(
         lowMA - close,
         0.0
    ) / atrValue


float buyPriceActionScore = (
     (flipUp ? 10.0 : 4.0) +
     f_scale01(
         buyBreakoutDistance,
         0.0,
         0.5
     ) * 6.0 +
     bodyRatio * 5.0 +
     buyCloseLocation * 4.0
)


float sellPriceActionScore = (
     (flipDown ? 10.0 : 4.0) +
     f_scale01(
         sellBreakoutDistance,
         0.0,
         0.5
     ) * 6.0 +
     bodyRatio * 5.0 +
     sellCloseLocation * 4.0
)


// ============================================================================
// SECTION 26: RSI MOMENTUM SCORE
// ============================================================================

float buyRSIDelta = rsiValue - 50.0

float sellRSIDelta = 50.0 - rsiValue


float buyRSIScore = (
     f_scale01(
         buyRSIDelta,
         0.0,
         20.0
     ) * 15.0 +
     (rsiValue > rsiValue[1] ? 5.0 : 0.0)
)


float sellRSIScore = (
     f_scale01(
         sellRSIDelta,
         0.0,
         20.0
     ) * 15.0 +
     (rsiValue < rsiValue[1] ? 5.0 : 0.0)
)


// ============================================================================
// SECTION 27: TREND / ADX SCORE
// ============================================================================

float diSum = math.max(
     diPlus + diMinus,
     0.0001
)


float buyDIDominance = f_clamp(
     (diPlus - diMinus) / diSum,
     0.0,
     1.0
)


float sellDIDominance = f_clamp(
     (diMinus - diPlus) / diSum,
     0.0,
     1.0
)


float buyTrendScore = (
     (diPlus > diMinus ? 6.0 : 0.0) +
     f_scale01(
         adxValue,
         adxThreshold,
         adxThreshold + 25.0
     ) * 10.0 +
     buyDIDominance * 5.0 +
     (adxValue > adxValue[1] ? 4.0 : 0.0)
)


float sellTrendScore = (
     (diMinus > diPlus ? 6.0 : 0.0) +
     f_scale01(
         adxValue,
         adxThreshold,
         adxThreshold + 25.0
     ) * 10.0 +
     sellDIDominance * 5.0 +
     (adxValue > adxValue[1] ? 4.0 : 0.0)
)


// ============================================================================
// SECTION 28: RELATIVE VOLUME SCORE
// ============================================================================

float volumeAverage = ta.sma(
     volume,
     20
)

float relativeVolume = na


if (
     not na(volume) and
     not na(volumeAverage) and
     volumeAverage > 0
)

    relativeVolume := volume / volumeAverage


float volumeScore = 7.5


if not na(relativeVolume)

    volumeScore := f_scale01(
         relativeVolume,
         0.8,
         2.0
    ) * 15.0


// ============================================================================
// SECTION 29: CHANNEL REGIME SCORE
// ============================================================================

float regimeScore = (
     f_scale01(
         normalizedWidth,
         widthThreshold * 0.7,
         widthThreshold * 2.0
     ) * 10.0 +
     (normalizedWidth > normalizedWidth[1] ? 5.0 : 0.0)
)


// ============================================================================
// SECTION 30: WICK / OVEREXTENSION PENALTIES
// ============================================================================

float buyOpposingWick = (
     high -
     math.max(
         open,
         close
     )
) / candleRange


float sellOpposingWick = (
     math.min(
         open,
         close
     ) -
     low
) / candleRange


float sslExtension = 0.0


if atrValue > 0

    sslExtension := math.abs(
         close - sslLine
    ) / atrValue


float buyWickPenalty = (
     (buyOpposingWick > 0.30 ? buyOpposingWick * 12.0 : 0.0) +
     f_scale01(
         sslExtension,
         2.0,
         3.5
     ) * 8.0
)


float sellWickPenalty = (
     (sellOpposingWick > 0.30 ? sellOpposingWick * 12.0 : 0.0) +
     f_scale01(
         sslExtension,
         2.0,
         3.5
     ) * 8.0
)


// ============================================================================
// SECTION 31: FINAL SCORES
// ============================================================================

float buyRawScore = (
     buyPriceActionScore +
     buyRSIScore +
     buyTrendScore +
     volumeScore +
     regimeScore -
     buyWickPenalty
)


float sellRawScore = (
     sellPriceActionScore +
     sellRSIScore +
     sellTrendScore +
     volumeScore +
     regimeScore -
     sellWickPenalty
)


int buyScore = int(
     math.round(
         f_clamp(
             buyRawScore,
             0.0,
             100.0
         )
     )
)


int sellScore = int(
     math.round(
         f_clamp(
             sellRawScore,
             0.0,
             100.0
         )
     )
)


int liveConfidence = sslState == 1 ? buyScore : sellScore


// ============================================================================
// SECTION 32: SOURCE BUY / SELL SIGNALS
// ============================================================================

bool buySignal = (
     barstate.isconfirmed and
     flipUp and
     buyRSI and
     buyADX and
     channelExpansionOK and
     buyHTFConfirmed and
     buyScore >= minimumScoreThreshold
)


bool sellSignal = (
     barstate.isconfirmed and
     flipDown and
     sellRSI and
     sellADX and
     channelExpansionOK and
     sellHTFConfirmed and
     sellScore >= minimumScoreThreshold
)


// ============================================================================
// SECTION 33: TREND WEAKENING
// ============================================================================

bool trendWeakening = (
     barstate.isconfirmed and
     (
         ta.crossunder(
             normalizedWidth,
             widthThreshold
         ) or
         ta.crossunder(
             adxValue,
             adxThreshold
         )
     )
)


// ============================================================================
// SECTION 34: POSITION STATE
// ============================================================================

bool flat = strategy.position_size == 0

bool inLong = strategy.position_size > 0

bool inShort = strategy.position_size < 0

bool newLongPosition = (
     inLong and
     strategy.position_size[1] <= 0
)

bool newShortPosition = (
     inShort and
     strategy.position_size[1] >= 0
)

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


// ============================================================================
// SECTION 35: COOLDOWN
// ============================================================================

var int lastExitBar = na


if positionJustClosed

    lastExitBar := bar_index


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


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

bool tradeLimitOK = (
     tradesToday <
     maximumTradesPerDay
)


// ============================================================================
// SECTION 37: COMMON ENTRY APPROVAL
// ============================================================================

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


// ============================================================================
// SECTION 38: FINAL LONG / SHORT ENTRIES
// ============================================================================

bool finalLongSignal = (
     commonEntryApproval and
     allowLong and
     buySignal
)


bool finalShortSignal = (
     commonEntryApproval and
     allowShort and
     sellSignal
)


// ============================================================================
// SECTION 39: LONG ENTRY
// ============================================================================

if finalLongSignal

    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment="SSL Matrix Long",
         alert_message="ENTRY_LONG_CNPS27"
    )

    tradesToday += 1


// ============================================================================
// SECTION 40: SHORT ENTRY
// ============================================================================

if finalShortSignal

    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment="SSL Matrix Short",
         alert_message="ENTRY_SHORT_CNPS27"
    )

    tradesToday += 1


// ============================================================================
// SECTION 41: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeLongSL = na

var float activeLongTP = na

var float activeShortSL = na

var float activeShortTP = na

var bool breakEvenActivated = false


// ============================================================================
// SECTION 42: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeLongSL := activeEntryPrice - stopLossPoints

    activeLongTP := activeEntryPrice + takeProfitPoints

    activeShortSL := na

    activeShortTP := na

    breakEvenActivated := false


// ============================================================================
// SECTION 43: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeShortSL := activeEntryPrice + stopLossPoints

    activeShortTP := activeEntryPrice - takeProfitPoints

    activeLongSL := na

    activeLongTP := na

    breakEvenActivated := false


// ============================================================================
// SECTION 44: LONG BREAK EVEN
// ============================================================================

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

    float longBreakEvenTrigger = activeEntryPrice + breakEvenTriggerPoints


    if high >= longBreakEvenTrigger

        float newLongStop = activeEntryPrice + breakEvenOffsetPoints


        if na(activeLongSL) or newLongStop > activeLongSL

            activeLongSL := newLongStop


        breakEvenActivated := true


// ============================================================================
// SECTION 45: SHORT BREAK EVEN
// ============================================================================

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

    float shortBreakEvenTrigger = activeEntryPrice - breakEvenTriggerPoints


    if low <= shortBreakEvenTrigger

        float newShortStop = activeEntryPrice - breakEvenOffsetPoints


        if na(activeShortSL) or newShortStop < activeShortSL

            activeShortSL := newShortStop


        breakEvenActivated := true


// ============================================================================
// SECTION 46: LONG SL / TP
// ============================================================================

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

    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=activeLongSL,
         limit=activeLongTP,
         alert_profit="EXIT_LONG_TP_CNPS27",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS27" : "EXIT_LONG_SL_CNPS27",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 47: SHORT SL / TP
// ============================================================================

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

    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=activeShortSL,
         limit=activeShortTP,
         alert_profit="EXIT_SHORT_TP_CNPS27",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS27" : "EXIT_SHORT_SL_CNPS27",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 48: OPTIONAL OPPOSITE-SIGNAL EXIT
// ============================================================================

bool longOppositeExit = (
     inLong and
     exitOnOppositeQualifiedSignal and
     sellSignal
)


bool shortOppositeExit = (
     inShort and
     exitOnOppositeQualifiedSignal and
     buySignal
)


if longOppositeExit

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


if shortOppositeExit

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


// ============================================================================
// SECTION 49: OPTIONAL TREND-WEAKENING EXIT
// ============================================================================

bool longWeakeningExit = (
     inLong and
     exitOnTrendWeakening and
     trendWeakening
)


bool shortWeakeningExit = (
     inShort and
     exitOnTrendWeakening and
     trendWeakening
)


if longWeakeningExit

    strategy.close(
         "Long",
         comment="Trend Weakening",
         alert_message="WEAKENING_EXIT_LONG_CNPS27"
    )


if shortWeakeningExit

    strategy.close(
         "Short",
         comment="Trend Weakening",
         alert_message="WEAKENING_EXIT_SHORT_CNPS27"
    )


// ============================================================================
// SECTION 50: POSITION CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeLongSL := na

    activeLongTP := na

    activeShortSL := na

    activeShortTP := na

    breakEvenActivated := false


// ============================================================================
// SECTION 51: 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_CNPS27"
        )


// ============================================================================
// SECTION 52: SSL DISPLAY
// ============================================================================

color bullishColor = color.rgb(
     0,
     230,
     118
)

color bearishColor = color.rgb(
     255,
     23,
     68
)

color sslColor = sslState == 1 ? bullishColor : bearishColor


plot(
     showSSL ? sslLine : na,
     title="SSL Line",
     color=sslColor,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showSSL ? highMA : na,
     title="SSL High MA",
     color=color.new(
         sslColor,
         55
     ),
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showSSL ? lowMA : na,
     title="SSL Low MA",
     color=color.new(
         sslColor,
         55
     ),
     linewidth=1,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 53: ACTIVE SL / TP
// ============================================================================

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


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


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


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


// ============================================================================
// SECTION 54: ENTRY MARKERS
// ============================================================================

plotshape(
     showSignalMarkers and finalLongSignal,
     title="Long Entry",
     style=shape.triangleup,
     location=location.belowbar,
     color=bullishColor,
     size=size.small,
     text="L",
     textcolor=color.black
)


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


// ============================================================================
// SECTION 55: OPTIONAL SCORE LABELS
// ============================================================================

if showScoreLabels and finalLongSignal

    string longLabelText = "BUY [" + f_grade(buyScore) + "] " + str.tostring(buyScore)

    label.new(
         bar_index,
         low - atrValue * 0.8,
         longLabelText,
         yloc=yloc.price,
         style=label.style_label_up,
         color=bullishColor,
         textcolor=color.black,
         size=size.small
    )


if showScoreLabels and finalShortSignal

    string shortLabelText = "SELL [" + f_grade(sellScore) + "] " + str.tostring(sellScore)

    label.new(
         bar_index,
         high + atrValue * 0.8,
         shortLabelText,
         yloc=yloc.price,
         style=label.style_label_down,
         color=bearishColor,
         textcolor=color.white,
         size=size.small
    )


// ============================================================================
// SECTION 56: DIAGNOSTIC COUNTERS
// ============================================================================

var int flipUpCount = 0

var int flipDownCount = 0

var int qualifiedBuyCount = 0

var int qualifiedSellCount = 0

var int entryLongCount = 0

var int entryShortCount = 0


if flipUp
    flipUpCount += 1


if flipDown
    flipDownCount += 1


if buySignal
    qualifiedBuyCount += 1


if sellSignal
    qualifiedSellCount += 1


if finalLongSignal
    entryLongCount += 1


if finalShortSignal
    entryShortCount += 1


// ============================================================================
// SECTION 57: DIAGNOSTICS
// ============================================================================

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


if showDiagnostics and barstate.islast

    string sslStateText = sslState == 1 ? "BULL" : "BEAR"

    string positionText = "FLAT"

    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


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


    table.cell(diagnostics, 0, 1, "Profile")
    table.cell(diagnostics, 1, 1, signalProfile)


    table.cell(diagnostics, 0, 2, "SSL State")
    table.cell(diagnostics, 1, 2, sslStateText)


    table.cell(diagnostics, 0, 3, "RSI")
    table.cell(diagnostics, 1, 3, str.tostring(rsiValue, "0.0"))


    table.cell(diagnostics, 0, 4, "ADX")
    table.cell(diagnostics, 1, 4, str.tostring(adxValue, "0.0"))


    table.cell(diagnostics, 0, 5, "ADX Threshold")
    table.cell(diagnostics, 1, 5, str.tostring(adxThreshold, "0.0"))


    table.cell(diagnostics, 0, 6, "DI+ / DI-")
    table.cell(diagnostics, 1, 6, str.tostring(diPlus, "0.0") + " / " + str.tostring(diMinus, "0.0"))


    table.cell(diagnostics, 0, 7, "Channel Width")
    table.cell(diagnostics, 1, 7, str.tostring(normalizedWidth, "0.00"))


    table.cell(diagnostics, 0, 8, "Width Threshold")
    table.cell(diagnostics, 1, 8, str.tostring(widthThreshold, "0.00"))


    table.cell(diagnostics, 0, 9, "Buy Score")
    table.cell(diagnostics, 1, 9, str.tostring(buyScore) + " / " + str.tostring(minimumScoreThreshold))


    table.cell(diagnostics, 0, 10, "Sell Score")
    table.cell(diagnostics, 1, 10, str.tostring(sellScore) + " / " + str.tostring(minimumScoreThreshold))


    table.cell(diagnostics, 0, 11, "HTF State")
    table.cell(diagnostics, 1, 11, useHTF ? str.tostring(htfState) : "OFF")


    table.cell(diagnostics, 0, 12, "Flip U / D")
    table.cell(diagnostics, 1, 12, str.tostring(flipUpCount) + " / " + str.tostring(flipDownCount))


    table.cell(diagnostics, 0, 13, "Qualified B / S")
    table.cell(diagnostics, 1, 13, str.tostring(qualifiedBuyCount) + " / " + str.tostring(qualifiedSellCount))


    table.cell(diagnostics, 0, 14, "Entry L / S")
    table.cell(diagnostics, 1, 14, str.tostring(entryLongCount) + " / " + str.tostring(entryShortCount))


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


    table.cell(diagnostics, 0, 16, "Confidence")
    table.cell(diagnostics, 1, 16, str.tostring(liveConfidence) + "%")


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