//@version=6

strategy(
     title="CNPS41 DYNAMIC GRID TREND REVERSION",
     shorttitle="CNPS41 GRID",
     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
)


// ============================================================================
// CNPS41 DYNAMIC GRID TREND REVERSION
//
// SOURCE CORE:
//
// BASELINE:
// HMA 150
//
// GRID STEP:
// ATR 100 x 1.6
//
// GRID:
// HMA +/- 1 Grid Step
// HMA +/- 2 Grid Steps
// HMA +/- 3 Grid Steps
// Optional up to +/- 5
//
// LONG SOURCE SIGNAL:
//
// Price is BELOW HMA
// + HMA is rising
// + Close crosses BACK ABOVE one of the lower grid levels
// + Signal spacing requirement is satisfied
// + Confirmed candle
//
// SHORT SOURCE SIGNAL:
//
// Price is ABOVE HMA
// + HMA is NOT rising
// + Close crosses BACK BELOW one of the upper grid levels
// + Signal spacing requirement is satisfied
// + Confirmed candle
//
// SIGNAL STRENGTH:
//
// Tier 1 = Buy / Sell
// Tier 2 = Buy Buy / Sell Sell
// Tier 3 = Must Buy / Must Sell
//
// Tier is based on:
// - Furthest grid crossed on the signal candle
// - Furthest wick stretch during the Minimum Bar Gap window
//
// DEFAULT CNPS EXECUTION:
//
// - Current chart timeframe
// - 3 contracts
// - 100M VND
// - Commission = 10,000 VND / contract
// - Slippage = 3 ticks
// - Fixed SL = 5 points
// - Fixed TP = 15 points
// - Entry 09:00 -> 14:30
// - Force close 14:45
// ============================================================================


// ============================================================================
// SECTION 1: BASELINE & GRID
// ============================================================================

hmaLength = input.int(
     150,
     title="HMA Length",
     minval=1,
     group="Dynamic Grid"
)

atrLength = input.int(
     100,
     title="ATR Length",
     minval=1,
     group="Dynamic Grid"
)

atrMultiplier = input.float(
     1.6,
     title="ATR Multiplier Step",
     minval=0.1,
     step=0.1,
     group="Dynamic Grid"
)

numberOfLevels = input.int(
     3,
     title="Grid Levels Each Side",
     minval=1,
     maxval=5,
     group="Dynamic Grid"
)


// ============================================================================
// SECTION 2: SOURCE SIGNAL SPACING
// ============================================================================

minimumSignalGap = input.int(
     10,
     title="Minimum Bars Between Source Signals",
     minval=1,
     maxval=200,
     group="Signal Logic"
)


// ============================================================================
// SECTION 3: SIGNAL TIER
// ============================================================================
//
// 1 = Buy / Sell
// 2 = Buy Buy / Sell Sell
// 3 = Must Buy / Must Sell
//
// Default 1 preserves all source signals.
//
// ============================================================================

minimumSignalTier = input.int(
     1,
     title="Minimum Signal Tier",
     minval=1,
     maxval=3,
     group="Signal Logic"
)


// ============================================================================
// SECTION 4: 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 5: DAILY TRADE CONTROL
// ============================================================================

maximumTradesPerDay = input.int(
     0,
     title="Maximum Trades Per Day (0 = Unlimited)",
     minval=0,
     maxval=50,
     group="Trade Control"
)

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


// ============================================================================
// SECTION 6: FIXED RISK
// ============================================================================

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 7: 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 8: OPTIONAL OPPOSITE SIGNAL EXIT
// ============================================================================
//
// OFF by default because the source indicator only defines signals.
// Fixed SL / TP remains the default CNPS exit.
//
// ============================================================================

exitOnOppositeSignal = input.bool(
     false,
     title="Exit On Opposite Source Signal?",
     group="Risk Management"
)


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

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

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

entryStartMinute = 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"
)

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 10: ALERTS
// ============================================================================

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


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

showHMA = input.bool(
     true,
     title="Show HMA?",
     group="Display"
)

showGrid = input.bool(
     false,
     title="Show Dynamic Grid?",
     group="Display"
)

showSignalMarkers = input.bool(
     false,
     title="Show Signal Markers?",
     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 CALCULATIONS
// ============================================================================

float centerLine = ta.hma(
     close,
     hmaLength
)

float gridStep = (
     ta.atr(atrLength) *
     atrMultiplier
)


// ============================================================================
// SECTION 13: DYNAMIC GRID LEVELS
// ============================================================================

float upper1 = centerLine + gridStep

float lower1 = centerLine - gridStep

float upper2 = centerLine + gridStep * 2.0

float lower2 = centerLine - gridStep * 2.0

float upper3 = centerLine + gridStep * 3.0

float lower3 = centerLine - gridStep * 3.0

float upper4 = centerLine + gridStep * 4.0

float lower4 = centerLine - gridStep * 4.0

float upper5 = centerLine + gridStep * 5.0

float lower5 = centerLine - gridStep * 5.0


// ============================================================================
// SECTION 14: NORMALIZED GRID POSITION
// ============================================================================

float oscillatorValue = 0.0


if (
     not na(gridStep) and
     gridStep != 0
)

    oscillatorValue := (
         close -
         centerLine
    ) / gridStep


// ============================================================================
// SECTION 15: SOURCE CROSS CALCULATIONS
// ============================================================================

bool crossBear1 = ta.crossunder(
     close,
     upper1
)

bool crossBear2 = ta.crossunder(
     close,
     upper2
)

bool crossBear3 = ta.crossunder(
     close,
     upper3
)

bool crossBear4 = ta.crossunder(
     close,
     upper4
)

bool crossBear5 = ta.crossunder(
     close,
     upper5
)


bool crossBull1 = ta.crossover(
     close,
     lower1
)

bool crossBull2 = ta.crossover(
     close,
     lower2
)

bool crossBull3 = ta.crossover(
     close,
     lower3
)

bool crossBull4 = ta.crossover(
     close,
     lower4
)

bool crossBull5 = ta.crossover(
     close,
     lower5
)


// ============================================================================
// SECTION 16: ACTIVE GRID LEVEL FILTER
// ============================================================================

bool rawBear1 = (
     crossBear1 and
     numberOfLevels >= 1
)

bool rawBear2 = (
     crossBear2 and
     numberOfLevels >= 2
)

bool rawBear3 = (
     crossBear3 and
     numberOfLevels >= 3
)

bool rawBear4 = (
     crossBear4 and
     numberOfLevels >= 4
)

bool rawBear5 = (
     crossBear5 and
     numberOfLevels >= 5
)


bool rawBull1 = (
     crossBull1 and
     numberOfLevels >= 1
)

bool rawBull2 = (
     crossBull2 and
     numberOfLevels >= 2
)

bool rawBull3 = (
     crossBull3 and
     numberOfLevels >= 3
)

bool rawBull4 = (
     crossBull4 and
     numberOfLevels >= 4
)

bool rawBull5 = (
     crossBull5 and
     numberOfLevels >= 5
)


bool rawBearSignal = (
     rawBear1 or
     rawBear2 or
     rawBear3 or
     rawBear4 or
     rawBear5
)


bool rawBullSignal = (
     rawBull1 or
     rawBull2 or
     rawBull3 or
     rawBull4 or
     rawBull5
)


// ============================================================================
// SECTION 17: FURTHEST GRID CROSS ON SIGNAL BAR
// ============================================================================

int bearCrossLevel = 0

int bullCrossLevel = 0


if rawBear5
    bearCrossLevel := 5

else if rawBear4
    bearCrossLevel := 4

else if rawBear3
    bearCrossLevel := 3

else if rawBear2
    bearCrossLevel := 2

else if rawBear1
    bearCrossLevel := 1


if rawBull5
    bullCrossLevel := 5

else if rawBull4
    bullCrossLevel := 4

else if rawBull3
    bullCrossLevel := 3

else if rawBull2
    bullCrossLevel := 2

else if rawBull1
    bullCrossLevel := 1


// ============================================================================
// SECTION 18: SOURCE HMA TREND STATE
// ============================================================================
//
// Exact source behavior:
//
// Green = HMA rising
//
// Red = NOT rising
//
// Therefore a perfectly flat HMA is classified as Red.
//
// ============================================================================

bool baselineGreen = (
     centerLine >
     centerLine[1]
)

bool baselineRed = (
     not baselineGreen
)


// ============================================================================
// SECTION 19: SOURCE TREND-ALIGNED QUALIFICATION
// ============================================================================
//
// SELL:
//
// Grid re-entry from above
// + HMA red
// + price remains above HMA
//
//
// BUY:
//
// Grid re-entry from below
// + HMA green
// + price remains below HMA
//
// ============================================================================

bool qualifiedBear = (
     rawBearSignal and
     baselineRed and
     close >
     centerLine
)


bool qualifiedBull = (
     rawBullSignal and
     baselineGreen and
     close <
     centerLine
)


// ============================================================================
// SECTION 20: SOURCE SIGNAL GAP
// ============================================================================

var int sourceLastSignalBar = -999999

bool sourceBearSignal = false

bool sourceBullSignal = false


bool sourceGapOK = (
     bar_index -
     sourceLastSignalBar >=
     minimumSignalGap
)


if (
     barstate.isconfirmed and
     sourceGapOK and
     (
         qualifiedBear or
         qualifiedBull
     )
)

    sourceBearSignal := qualifiedBear

    sourceBullSignal := qualifiedBull

    sourceLastSignalBar := bar_index


// ============================================================================
// SECTION 21: WICK STRETCH
// ============================================================================
//
// Measures how far the market stretched from HMA during the recent
// Minimum Signal Gap window.
//
// ============================================================================

float highReach = 0.0

float lowReach = 0.0


if (
     not na(gridStep) and
     gridStep != 0
)

    highReach := (
         high -
         centerLine
    ) / gridStep


    lowReach := (
         centerLine -
         low
    ) / gridStep


float maximumHighReach = ta.highest(
     highReach,
     minimumSignalGap
)

float maximumLowReach = ta.highest(
     lowReach,
     minimumSignalGap
)


// ============================================================================
// SECTION 22: REACH LEVEL
// ============================================================================

int bearReachLevel = int(
     math.floor(
         maximumHighReach
     )
)

int bullReachLevel = int(
     math.floor(
         maximumLowReach
     )
)


// ============================================================================
// SECTION 23: SOURCE SIGNAL TIER
// ============================================================================

int bearTier = 0

int bullTier = 0


if sourceBearSignal

    bearTier := math.max(
         bearCrossLevel,
         bearReachLevel
    )


    if bearTier < 1
        bearTier := 1


    if bearTier > 3
        bearTier := 3


if sourceBullSignal

    bullTier := math.max(
         bullCrossLevel,
         bullReachLevel
    )


    if bullTier < 1
        bullTier := 1


    if bullTier > 3
        bullTier := 3


// ============================================================================
// SECTION 24: TIER FILTER
// ============================================================================

bool longTierOK = (
     sourceBullSignal and
     bullTier >=
     minimumSignalTier
)


bool shortTierOK = (
     sourceBearSignal and
     bearTier >=
     minimumSignalTier
)


// ============================================================================
// SECTION 25: SESSION ENGINE
// ============================================================================

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

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

int entryStartTotal = (
     entryStartHour *
     60 +
     entryStartMinute
)

int lastEntryTotal = (
     lastEntryHour *
     60 +
     lastEntryMinute
)

int forceCloseTotal = (
     forceCloseHour *
     60 +
     forceCloseMinute
)


bool entrySession = (
     currentMinute >=
     entryStartTotal and
     currentMinute <=
     lastEntryTotal
)


// ============================================================================
// SECTION 26: 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 27: 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 28: COOLDOWN AFTER EXIT
// ============================================================================

var int lastExitBar = na


if positionJustClosed

    lastExitBar := bar_index


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


// ============================================================================
// SECTION 29: DAILY LIMIT
// ============================================================================

bool tradeLimitOK = (
     maximumTradesPerDay == 0 or
     tradesToday <
     maximumTradesPerDay
)


// ============================================================================
// SECTION 30: COMMON ENTRY APPROVAL
// ============================================================================

bool commonEntryOK = (
     flat and
     entrySession and
     currentCloseMinute <
     forceCloseTotal and
     cooldownOK and
     tradeLimitOK and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 31: FINAL LONG SIGNAL
// ============================================================================

bool finalLongSignal = (
     commonEntryOK and
     allowLong and
     longTierOK
)


// ============================================================================
// SECTION 32: FINAL SHORT SIGNAL
// ============================================================================

bool finalShortSignal = (
     commonEntryOK and
     allowShort and
     shortTierOK and
     not finalLongSignal
)


// ============================================================================
// SECTION 33: LONG ENTRY
// ============================================================================

if finalLongSignal

    string longEntryAlert = "ENTRY_LONG_CNPS41"


    if bullTier == 2
        longEntryAlert := "ENTRY_LONG_BUY_BUY_CNPS41"


    else if bullTier == 3
        longEntryAlert := "ENTRY_LONG_MUST_BUY_CNPS41"


    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment=bullTier == 3 ? "Must Buy" : bullTier == 2 ? "Buy Buy" : "Buy",
         alert_message=longEntryAlert
    )


    tradesToday += 1


// ============================================================================
// SECTION 34: SHORT ENTRY
// ============================================================================

if finalShortSignal

    string shortEntryAlert = "ENTRY_SHORT_CNPS41"


    if bearTier == 2
        shortEntryAlert := "ENTRY_SHORT_SELL_SELL_CNPS41"


    else if bearTier == 3
        shortEntryAlert := "ENTRY_SHORT_MUST_SELL_CNPS41"


    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment=bearTier == 3 ? "Must Sell" : bearTier == 2 ? "Sell Sell" : "Sell",
         alert_message=shortEntryAlert
    )


    tradesToday += 1


// ============================================================================
// SECTION 35: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeStop = na

var float activeTarget = na

var int activeEntryBar = na

var int activeTier = 0

var bool breakEvenActivated = false


// ============================================================================
// SECTION 36: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := (
         strategy.position_avg_price
    )

    activeStop := (
         activeEntryPrice -
         stopLossPoints
    )

    activeTarget := (
         activeEntryPrice +
         takeProfitPoints
    )

    activeEntryBar := bar_index

    activeTier := bullTier

    breakEvenActivated := false


// ============================================================================
// SECTION 37: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := (
         strategy.position_avg_price
    )

    activeStop := (
         activeEntryPrice +
         stopLossPoints
    )

    activeTarget := (
         activeEntryPrice -
         takeProfitPoints
    )

    activeEntryBar := bar_index

    activeTier := bearTier

    breakEvenActivated := false


// ============================================================================
// SECTION 38: LONG BREAK EVEN
// ============================================================================

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

    float longBETrigger = (
         activeEntryPrice +
         breakEvenTriggerPoints
    )


    if high >= longBETrigger

        float longBECandidate = (
             activeEntryPrice +
             breakEvenOffsetPoints
        )


        activeStop := math.max(
             activeStop,
             longBECandidate
        )


        breakEvenActivated := true


// ============================================================================
// SECTION 39: SHORT BREAK EVEN
// ============================================================================

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

    float shortBETrigger = (
         activeEntryPrice -
         breakEvenTriggerPoints
    )


    if low <= shortBETrigger

        float shortBECandidate = (
             activeEntryPrice -
             breakEvenOffsetPoints
        )


        activeStop := math.min(
             activeStop,
             shortBECandidate
        )


        breakEvenActivated := true


// ============================================================================
// SECTION 40: LONG PROTECTIVE EXIT
// ============================================================================

if (
     inLong and
     not na(activeStop) and
     not na(activeTarget)
)

    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=activeStop,
         limit=activeTarget,
         alert_profit="EXIT_LONG_TP_CNPS41",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS41" : "EXIT_LONG_SL_CNPS41",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 41: SHORT PROTECTIVE EXIT
// ============================================================================

if (
     inShort and
     not na(activeStop) and
     not na(activeTarget)
)

    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=activeStop,
         limit=activeTarget,
         alert_profit="EXIT_SHORT_TP_CNPS41",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS41" : "EXIT_SHORT_SL_CNPS41",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 42: OPTIONAL OPPOSITE SIGNAL EXIT - LONG
// ============================================================================

bool oppositeExitLong = (
     inLong and
     exitOnOppositeSignal and
     sourceBearSignal and
     barstate.isconfirmed
)


if oppositeExitLong

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


// ============================================================================
// SECTION 43: OPTIONAL OPPOSITE SIGNAL EXIT - SHORT
// ============================================================================

bool oppositeExitShort = (
     inShort and
     exitOnOppositeSignal and
     sourceBullSignal and
     barstate.isconfirmed
)


if oppositeExitShort

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


// ============================================================================
// SECTION 44: CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeStop := na

    activeTarget := na

    activeEntryBar := na

    activeTier := 0

    breakEvenActivated := false


// ============================================================================
// SECTION 45: 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_CNPS41"
    )


// ============================================================================
// SECTION 46: HMA DISPLAY
// ============================================================================

color hmaColor = (
     baselineGreen
     ? color.green
     : color.red
)


plot(
     showHMA
     ? centerLine
     : na,
     title="Central HMA",
     color=hmaColor,
     linewidth=2
)


// ============================================================================
// SECTION 47: GRID DISPLAY
// ============================================================================

plot(
     showGrid and numberOfLevels >= 1 ? upper1 : na,
     title="Upper 1",
     color=color.new(color.green, 55),
     linewidth=1
)

plot(
     showGrid and numberOfLevels >= 1 ? lower1 : na,
     title="Lower 1",
     color=color.new(color.red, 55),
     linewidth=1
)


plot(
     showGrid and numberOfLevels >= 2 ? upper2 : na,
     title="Upper 2",
     color=color.new(color.green, 40),
     linewidth=1
)

plot(
     showGrid and numberOfLevels >= 2 ? lower2 : na,
     title="Lower 2",
     color=color.new(color.red, 40),
     linewidth=1
)


plot(
     showGrid and numberOfLevels >= 3 ? upper3 : na,
     title="Upper 3",
     color=color.new(color.green, 25),
     linewidth=2
)

plot(
     showGrid and numberOfLevels >= 3 ? lower3 : na,
     title="Lower 3",
     color=color.new(color.red, 25),
     linewidth=2
)


plot(
     showGrid and numberOfLevels >= 4 ? upper4 : na,
     title="Upper 4",
     color=color.new(color.green, 15),
     linewidth=2
)

plot(
     showGrid and numberOfLevels >= 4 ? lower4 : na,
     title="Lower 4",
     color=color.new(color.red, 15),
     linewidth=2
)


plot(
     showGrid and numberOfLevels >= 5 ? upper5 : na,
     title="Upper 5",
     color=color.green,
     linewidth=2
)

plot(
     showGrid and numberOfLevels >= 5 ? lower5 : na,
     title="Lower 5",
     color=color.red,
     linewidth=2
)


// ============================================================================
// SECTION 48: ACTIVE STOP
// ============================================================================

plot(
     showActiveSLTP and
     strategy.position_size != 0
     ? activeStop
     : na,
     title="Active Stop",
     color=color.red,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 49: ACTIVE TARGET
// ============================================================================

plot(
     showActiveSLTP and
     strategy.position_size != 0
     ? activeTarget
     : na,
     title="Active Target",
     color=color.green,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 50: SOURCE SIGNAL MARKERS
// ============================================================================

plotshape(
     showSignalMarkers and sourceBullSignal and bullTier == 1,
     title="Buy",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="BUY"
)


plotshape(
     showSignalMarkers and sourceBullSignal and bullTier == 2,
     title="Buy Buy",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.small,
     text="BUY2"
)


plotshape(
     showSignalMarkers and sourceBullSignal and bullTier == 3,
     title="Must Buy",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.normal,
     text="MUST"
)


plotshape(
     showSignalMarkers and sourceBearSignal and bearTier == 1,
     title="Sell",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="SELL"
)


plotshape(
     showSignalMarkers and sourceBearSignal and bearTier == 2,
     title="Sell Sell",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.small,
     text="SELL2"
)


plotshape(
     showSignalMarkers and sourceBearSignal and bearTier == 3,
     title="Must Sell",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.normal,
     text="MUST"
)


// ============================================================================
// SECTION 51: DATA WINDOW OUTPUTS
// ============================================================================

plot(
     bullTier == 1 ? 1 : 0,
     title="Buy",
     display=display.data_window
)

plot(
     bullTier == 2 ? 1 : 0,
     title="Buy Buy",
     display=display.data_window
)

plot(
     bullTier == 3 ? 1 : 0,
     title="Must Buy",
     display=display.data_window
)


plot(
     bearTier == 1 ? 1 : 0,
     title="Sell",
     display=display.data_window
)

plot(
     bearTier == 2 ? 1 : 0,
     title="Sell Sell",
     display=display.data_window
)

plot(
     bearTier == 3 ? 1 : 0,
     title="Must Sell",
     display=display.data_window
)


plot(
     oscillatorValue,
     title="Grid Position",
     display=display.data_window
)


// ============================================================================
// SECTION 52: DIAGNOSTIC COUNTERS
// ============================================================================

var int buyCount = 0

var int buyBuyCount = 0

var int mustBuyCount = 0

var int sellCount = 0

var int sellSellCount = 0

var int mustSellCount = 0


if sourceBullSignal

    if bullTier == 1
        buyCount += 1

    else if bullTier == 2
        buyBuyCount += 1

    else if bullTier == 3
        mustBuyCount += 1


if sourceBearSignal

    if bearTier == 1
        sellCount += 1

    else if bearTier == 2
        sellSellCount += 1

    else if bearTier == 3
        mustSellCount += 1


// ============================================================================
// SECTION 53: DIAGNOSTICS
// ============================================================================

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


if (
     showDiagnostics and
     barstate.islast
)

    string trendText = (
         baselineGreen
         ? "GREEN / RISING"
         : "RED / NOT RISING"
    )


    string positionText = "FLAT"


    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


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

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


    table.cell(
         diagnostics,
         0,
         1,
         "HMA Trend"
    )

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


    table.cell(
         diagnostics,
         0,
         2,
         "Grid Position"
    )

    table.cell(
         diagnostics,
         1,
         2,
         str.tostring(
             oscillatorValue,
             "0.00"
         )
    )


    table.cell(
         diagnostics,
         0,
         3,
         "Grid Step"
    )

    table.cell(
         diagnostics,
         1,
         3,
         str.tostring(
             gridStep,
             "0.00"
         )
    )


    table.cell(
         diagnostics,
         0,
         4,
         "Bull Cross Level"
    )

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


    table.cell(
         diagnostics,
         0,
         5,
         "Bear Cross Level"
    )

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


    table.cell(
         diagnostics,
         0,
         6,
         "Bull Reach"
    )

    table.cell(
         diagnostics,
         1,
         6,
         str.tostring(
             bullReachLevel
         )
    )


    table.cell(
         diagnostics,
         0,
         7,
         "Bear Reach"
    )

    table.cell(
         diagnostics,
         1,
         7,
         str.tostring(
             bearReachLevel
         )
    )


    table.cell(
         diagnostics,
         0,
         8,
         "Bull Tier"
    )

    table.cell(
         diagnostics,
         1,
         8,
         str.tostring(
             bullTier
         )
    )


    table.cell(
         diagnostics,
         0,
         9,
         "Bear Tier"
    )

    table.cell(
         diagnostics,
         1,
         9,
         str.tostring(
             bearTier
         )
    )


    table.cell(
         diagnostics,
         0,
         10,
         "Buy"
    )

    table.cell(
         diagnostics,
         1,
         10,
         str.tostring(
             buyCount
         )
    )


    table.cell(
         diagnostics,
         0,
         11,
         "Buy Buy"
    )

    table.cell(
         diagnostics,
         1,
         11,
         str.tostring(
             buyBuyCount
         )
    )


    table.cell(
         diagnostics,
         0,
         12,
         "Must Buy"
    )

    table.cell(
         diagnostics,
         1,
         12,
         str.tostring(
             mustBuyCount
         )
    )


    table.cell(
         diagnostics,
         0,
         13,
         "Sell"
    )

    table.cell(
         diagnostics,
         1,
         13,
         str.tostring(
             sellCount
         )
    )


    table.cell(
         diagnostics,
         0,
         14,
         "Sell Sell"
    )

    table.cell(
         diagnostics,
         1,
         14,
         str.tostring(
             sellSellCount
         )
    )


    table.cell(
         diagnostics,
         0,
         15,
         "Must Sell"
    )

    table.cell(
         diagnostics,
         1,
         15,
         str.tostring(
             mustSellCount
         )
    )


    table.cell(
         diagnostics,
         0,
         16,
         "Trades Today"
    )

    table.cell(
         diagnostics,
         1,
         16,
         maximumTradesPerDay == 0
         ? str.tostring(tradesToday) + " / INF"
         : str.tostring(tradesToday) + " / " + str.tostring(maximumTradesPerDay)
    )


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

    table.cell(
         diagnostics,
         1,
         17,
         positionText
    )