//@version=6
strategy(
     title="CNPS28 AUTO RANGE V2 CONFIRMED",
     shorttitle="CNPS28 RANGE V2",
     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
)


// ============================================================================
// CNPS28 AUTO RANGE V2 CONFIRMED
//
// SOURCE-DERIVED RANGE ENGINE:
// - Multi-scale range scan
// - Percentile boundaries
// - Volatility compression
// - Rotation
// - Boundary touches
// - Drift filter
// - Containment
// - Breakout / breakdown
// - Deviation / liquidity sweep
//
// V2 EXECUTION:
//
// BREAKOUT:
// Source breakout
// -> ARM
// -> wait for boundary retest
// -> continuation confirmation
// -> enter
// -> structure SL
// -> 2R TP
//
// DEVIATION:
// Source deviation
// -> ARM
// -> wait for reversal confirmation
// -> enter
// -> stop beyond sweep
// -> target Range EQ
//
// COST AWARE:
// - Max 2 trades / day
// - Cooldown 5 bars
// - No break-even default
// - Current chart timeframe
// - 3 contracts
// ============================================================================


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

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

signalMode = input.string(
     "Both",
     title="Signal Mode",
     options=[
         "Both",
         "Breakout Only",
         "Deviation Only"
     ],
     group="Trade Control"
)

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

bool allowBreakout = signalMode == "Both" or signalMode == "Breakout Only"
bool allowDeviation = signalMode == "Both" or signalMode == "Deviation Only"

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

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


// ============================================================================
// SECTION 2: RANGE SCAN
// ============================================================================

baseScanLength = input.int(
     20,
     title="Base Scan Length",
     minval=5,
     maxval=200,
     group="Range Scan"
)

scanScaleMode = input.string(
     "Auto Multi-Scale",
     title="Scan Scale Mode",
     options=[
         "Base (1x)",
         "Medium (2x)",
         "Wide (3x)",
         "Auto Multi-Scale"
     ],
     group="Range Scan"
)

boundaryPercentile = input.float(
     90.0,
     title="Boundary Percentile %",
     minval=50.0,
     maxval=99.0,
     step=1.0,
     group="Range Scan"
)

atrLength = input.int(
     200,
     title="ATR Length",
     minval=10,
     group="Range Scan"
)


// ============================================================================
// SECTION 3: RANGE QUALITY
// ============================================================================

volatilityCompressionThreshold = input.float(
     40.0,
     title="Volatility Compression Threshold %",
     minval=1.0,
     maxval=100.0,
     step=1.0,
     group="Range Quality"
)

minimumRotationRate = input.float(
     0.18,
     title="Minimum Rotation Rate",
     minval=0.01,
     maxval=1.0,
     step=0.01,
     group="Range Quality"
)

minimumTouches = input.int(
     2,
     title="Minimum Touches Per Boundary",
     minval=1,
     maxval=10,
     group="Range Quality"
)

maximumDrift = input.float(
     0.45,
     title="Maximum Drift / ATR",
     minval=0.05,
     maxval=2.0,
     step=0.05,
     group="Range Quality"
)

minimumContainment = input.float(
     70.0,
     title="Minimum Containment %",
     minval=50.0,
     maxval=100.0,
     step=5.0,
     group="Range Quality"
)


// ============================================================================
// SECTION 4: SOURCE BREAKOUT / DEVIATION
// ============================================================================

breakoutBufferMultiplier = input.float(
     0.15,
     title="Breakout Buffer x ATR",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Source Signals"
)

deviationWindow = input.int(
     10,
     title="Deviation Window",
     minval=1,
     maxval=50,
     group="Source Signals"
)

absorbOvershootMultiplier = input.float(
     0.25,
     title="Absorb Overshoot x ATR",
     minval=0.05,
     maxval=1.0,
     step=0.05,
     group="Source Signals"
)


// ============================================================================
// SECTION 5: BREAKOUT CONFIRMATION
// ============================================================================

breakoutSetupLifetime = input.int(
     8,
     title="Breakout Setup Lifetime",
     minval=1,
     maxval=50,
     group="Breakout Confirmation"
)

breakoutRetestToleranceATR = input.float(
     0.20,
     title="Retest Tolerance x ATR",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Breakout Confirmation"
)

breakoutMinimumBodyRatio = input.float(
     0.50,
     title="Confirmation Body / Range",
     minval=0.10,
     maxval=1.0,
     step=0.05,
     group="Breakout Confirmation"
)

breakoutRequirePreviousBreak = input.bool(
     true,
     title="Require Previous High / Low Break?",
     group="Breakout Confirmation"
)


// ============================================================================
// SECTION 6: BREAKOUT RISK
// ============================================================================

breakoutStopBufferATR = input.float(
     0.10,
     title="Breakout Stop Buffer x ATR",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Breakout Risk"
)

breakoutMinimumRisk = input.float(
     2.5,
     title="Minimum Breakout Risk Points",
     minval=0.5,
     step=0.5,
     group="Breakout Risk"
)

breakoutMaximumRisk = input.float(
     7.0,
     title="Maximum Breakout Risk Points",
     minval=1.0,
     step=0.5,
     group="Breakout Risk"
)

breakoutRewardRisk = input.float(
     2.0,
     title="Breakout Reward / Risk",
     minval=1.0,
     maxval=5.0,
     step=0.25,
     group="Breakout Risk"
)


// ============================================================================
// SECTION 7: DEVIATION CONFIRMATION
// ============================================================================

deviationConfirmationWindow = input.int(
     3,
     title="Deviation Confirmation Window",
     minval=1,
     maxval=20,
     group="Deviation Confirmation"
)

deviationMinimumBodyRatio = input.float(
     0.40,
     title="Deviation Confirmation Body / Range",
     minval=0.10,
     maxval=1.0,
     step=0.05,
     group="Deviation Confirmation"
)

deviationRequirePreviousBreak = input.bool(
     true,
     title="Require Previous High / Low Break?",
     group="Deviation Confirmation"
)

deviationRequireQuartileReclaim = input.bool(
     true,
     title="Require Q25 / Q75 Reclaim?",
     group="Deviation Confirmation"
)


// ============================================================================
// SECTION 8: DEVIATION RISK
// ============================================================================

deviationStopBufferATR = input.float(
     0.10,
     title="Deviation Stop Buffer x ATR",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Deviation Risk"
)

minimumDeviationRR = input.float(
     1.20,
     title="Minimum Reward / Risk To EQ",
     minval=0.50,
     maxval=5.0,
     step=0.10,
     group="Deviation Risk"
)

maximumDeviationRisk = input.float(
     7.0,
     title="Maximum Deviation Risk Points",
     minval=1.0,
     step=0.5,
     group="Deviation Risk"
)


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

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

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

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

morningEndHour = input.int(
     11,
     title="Morning End Hour",
     minval=0,
     maxval=23,
     group="Session"
)

morningEndMinute = input.int(
     20,
     title="Morning End Minute",
     minval=0,
     maxval=59,
     group="Session"
)

afternoonStartHour = input.int(
     13,
     title="Afternoon Start Hour",
     minval=0,
     maxval=23,
     group="Session"
)

afternoonStartMinute = input.int(
     5,
     title="Afternoon 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(
     20,
     title="Last Entry Minute",
     minval=0,
     maxval=59,
     group="Session"
)

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

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


// ============================================================================
// SECTION 10: ALERTS
// ============================================================================

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


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

showRangeLevels = input.bool(
     true,
     title="Show Range Levels?",
     group="Display"
)

showSetupLevels = input.bool(
     false,
     title="Show Armed Setup Levels?",
     group="Display"
)

showSignals = 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: STATUS CONSTANTS
// ============================================================================

int STATUS_CONSOLIDATION = 0
int STATUS_BREAKOUT_BULL = 1
int STATUS_BREAKOUT_BEAR = 2
int STATUS_DEVIATION_BULL = 3
int STATUS_DEVIATION_BEAR = 4


// ============================================================================
// SECTION 13: RANGE TEST FUNCTION
// ============================================================================

f_testCandidateRange(
     int length,
     float boundaryPct,
     float atrValue,
     float maximumVolRank,
     float minimumRotRate,
     int requiredTouches,
     float maximumDriftValue,
     float minimumContainmentRate
) =>

    bool valid = false
    float topCandidate = 0.0
    float bottomCandidate = 0.0
    float compressionRank = 0.0
    float rotationRate = 0.0
    int topTouches = 0
    int bottomTouches = 0
    float normalizedDrift = 0.0
    float containmentRate = 0.0

    if length >= 5 and not na(atrValue) and atrValue > 0

        topCandidate := ta.percentile_linear_interpolation(high, length, boundaryPct)
        bottomCandidate := ta.percentile_linear_interpolation(low, length, 100.0 - boundaryPct)

        float rangeHeight = topCandidate - bottomCandidate

        if rangeHeight > 0

            float normalizedHeight = rangeHeight / (atrValue * math.sqrt(length))

            compressionRank := ta.percentrank(normalizedHeight, atrLength)

            bool compressed = not na(compressionRank) and compressionRank <= maximumVolRank

            float midpoint = (topCandidate + bottomCandidate) / 2.0

            int crossingCount = 0

            for i = 0 to length - 2

                bool crossedUp = close[i] > midpoint and close[i + 1] <= midpoint
                bool crossedDown = close[i] < midpoint and close[i + 1] >= midpoint

                if crossedUp or crossedDown
                    crossingCount += 1

            rotationRate := float(crossingCount) / float(length)

            bool rotating = rotationRate >= minimumRotRate

            float touchBuffer = atrValue * 0.10

            bool previousTopTouch = false
            bool previousBottomTouch = false

            for i = length - 1 to 0

                bool currentTopTouch = high[i] >= topCandidate - touchBuffer

                if currentTopTouch and not previousTopTouch
                    topTouches += 1

                previousTopTouch := currentTopTouch

                bool currentBottomTouch = low[i] <= bottomCandidate + touchBuffer

                if currentBottomTouch and not previousBottomTouch
                    bottomTouches += 1

                previousBottomTouch := currentBottomTouch

            bool enoughTouches = topTouches >= requiredTouches and bottomTouches >= requiredTouches

            float regressionCurrent = ta.linreg(close, length, 0)

            float regressionPrevious = ta.linreg(close, length, 1)

            float regressionSlope = regressionCurrent - regressionPrevious

            normalizedDrift := math.abs(regressionSlope) * float(length) / atrValue

            bool flatEnough = normalizedDrift <= maximumDriftValue

            int insideCount = 0

            for i = 0 to length - 1

                if close[i] >= bottomCandidate and close[i] <= topCandidate
                    insideCount += 1

            containmentRate := float(insideCount) / float(length) * 100.0

            bool contained = containmentRate >= minimumContainmentRate

            valid := compressed and rotating and enoughTouches and flatEnough and contained

    [
         valid,
         topCandidate,
         bottomCandidate,
         compressionRank,
         rotationRate,
         topTouches,
         bottomTouches,
         normalizedDrift,
         containmentRate
    ]


// ============================================================================
// SECTION 14: ATR
// ============================================================================

float atrValue = ta.atr(atrLength)


// ============================================================================
// SECTION 15: SCAN CANDIDATES
// ============================================================================

int length1 = baseScanLength
int length2 = baseScanLength * 2
int length3 = baseScanLength * 3


[
     valid1,
     top1,
     bottom1,
     rank1,
     rotation1,
     topTouches1,
     bottomTouches1,
     drift1,
     containment1
] = f_testCandidateRange(
     length1,
     boundaryPercentile,
     atrValue,
     volatilityCompressionThreshold,
     minimumRotationRate,
     minimumTouches,
     maximumDrift,
     minimumContainment
)


[
     valid2,
     top2,
     bottom2,
     rank2,
     rotation2,
     topTouches2,
     bottomTouches2,
     drift2,
     containment2
] = f_testCandidateRange(
     length2,
     boundaryPercentile,
     atrValue,
     volatilityCompressionThreshold,
     minimumRotationRate,
     minimumTouches,
     maximumDrift,
     minimumContainment
)


[
     valid3,
     top3,
     bottom3,
     rank3,
     rotation3,
     topTouches3,
     bottomTouches3,
     drift3,
     containment3
] = f_testCandidateRange(
     length3,
     boundaryPercentile,
     atrValue,
     volatilityCompressionThreshold,
     minimumRotationRate,
     minimumTouches,
     maximumDrift,
     minimumContainment
)


// ============================================================================
// SECTION 16: SELECT BEST RANGE
// ============================================================================

bool detectedValid = false

int detectedLength = length1

float detectedTop = 0.0
float detectedBottom = 0.0
float detectedRank = 0.0
float detectedRotation = 0.0
float detectedContainment = 0.0


if scanScaleMode == "Base (1x)"

    if valid1
        detectedValid := true
        detectedLength := length1
        detectedTop := top1
        detectedBottom := bottom1
        detectedRank := rank1
        detectedRotation := rotation1
        detectedContainment := containment1


else if scanScaleMode == "Medium (2x)"

    if valid2
        detectedValid := true
        detectedLength := length2
        detectedTop := top2
        detectedBottom := bottom2
        detectedRank := rank2
        detectedRotation := rotation2
        detectedContainment := containment2


else if scanScaleMode == "Wide (3x)"

    if valid3
        detectedValid := true
        detectedLength := length3
        detectedTop := top3
        detectedBottom := bottom3
        detectedRank := rank3
        detectedRotation := rotation3
        detectedContainment := containment3


else

    if valid3
        detectedValid := true
        detectedLength := length3
        detectedTop := top3
        detectedBottom := bottom3
        detectedRank := rank3
        detectedRotation := rotation3
        detectedContainment := containment3

    else if valid2
        detectedValid := true
        detectedLength := length2
        detectedTop := top2
        detectedBottom := bottom2
        detectedRank := rank2
        detectedRotation := rotation2
        detectedContainment := containment2

    else if valid1
        detectedValid := true
        detectedLength := length1
        detectedTop := top1
        detectedBottom := bottom1
        detectedRank := rank1
        detectedRotation := rotation1
        detectedContainment := containment1


// ============================================================================
// SECTION 17: ACTIVE RANGE STATE
// ============================================================================

var bool activeRangeExists = false

var float activeRangeTop = na
var float activeRangeBottom = na

var int activeRangeStartBar = na
var int activeRangeStatus = STATUS_CONSOLIDATION
var int activeRangeBreakoutBar = na
var int activeRangeScanLength = na


// ============================================================================
// SECTION 18: SOURCE SIGNALS
// ============================================================================

bool sourceBullBreakout = false
bool sourceBearBreakout = false

bool sourceBullDeviation = false
bool sourceBearDeviation = false

bool newRangeCreated = false


// ============================================================================
// SECTION 19: BUFFERS
// ============================================================================

float breakoutBuffer = atrValue * breakoutBufferMultiplier

float absorptionBuffer = atrValue * absorbOvershootMultiplier


// ============================================================================
// SECTION 20: SESSION ENGINE
// ============================================================================

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

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

int morningStartTotal = morningStartHour * 60 + morningStartMinute

int morningEndTotal = morningEndHour * 60 + morningEndMinute

int afternoonStartTotal = afternoonStartHour * 60 + afternoonStartMinute

int lastEntryTotal = lastEntryHour * 60 + lastEntryMinute

int sessionEndTotal = sessionEndHour * 60 + sessionEndMinute

bool morningSession = currentMinute >= morningStartTotal and currentMinute <= morningEndTotal

bool afternoonSession = currentMinute >= afternoonStartTotal and currentMinute <= lastEntryTotal

bool entrySession = morningSession or afternoonSession


// ============================================================================
// SECTION 21: 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 22: ABSORB SMALL OVERSHOOTS
// ============================================================================

if activeRangeExists and activeRangeStatus == STATUS_CONSOLIDATION

    if high > activeRangeTop and high - activeRangeTop <= absorptionBuffer and close <= activeRangeTop
        activeRangeTop := high

    if low < activeRangeBottom and activeRangeBottom - low <= absorptionBuffer and close >= activeRangeBottom
        activeRangeBottom := low


// ============================================================================
// SECTION 23: CONSOLIDATION SIGNAL ENGINE
// ============================================================================

if activeRangeExists and activeRangeStatus == STATUS_CONSOLIDATION

    if close > activeRangeTop + breakoutBuffer

        activeRangeStatus := STATUS_BREAKOUT_BULL

        activeRangeBreakoutBar := bar_index

        sourceBullBreakout := true


    else if close < activeRangeBottom - breakoutBuffer

        activeRangeStatus := STATUS_BREAKOUT_BEAR

        activeRangeBreakoutBar := bar_index

        sourceBearBreakout := true


    else if high > activeRangeTop + breakoutBuffer and close < activeRangeTop

        activeRangeStatus := STATUS_DEVIATION_BEAR

        sourceBearDeviation := true


    else if low < activeRangeBottom - breakoutBuffer and close > activeRangeBottom

        activeRangeStatus := STATUS_DEVIATION_BULL

        sourceBullDeviation := true


// ============================================================================
// SECTION 24: MULTI-BAR FAKEOUT
// ============================================================================

else if activeRangeExists and activeRangeStatus == STATUS_BREAKOUT_BULL

    int barsSinceBreakout = bar_index - activeRangeBreakoutBar

    if barsSinceBreakout <= deviationWindow

        if close < activeRangeTop

            activeRangeStatus := STATUS_DEVIATION_BEAR

            sourceBearDeviation := true

    else

        activeRangeExists := false


else if activeRangeExists and activeRangeStatus == STATUS_BREAKOUT_BEAR

    int barsSinceBreakdown = bar_index - activeRangeBreakoutBar

    if barsSinceBreakdown <= deviationWindow

        if close > activeRangeBottom

            activeRangeStatus := STATUS_DEVIATION_BULL

            sourceBullDeviation := true

    else

        activeRangeExists := false


// ============================================================================
// SECTION 25: CLOSE DEVIATED RANGE ON STRONG RE-BREAK
// ============================================================================

else if (
     activeRangeExists and
     (
         activeRangeStatus == STATUS_DEVIATION_BULL or
         activeRangeStatus == STATUS_DEVIATION_BEAR
     )
)

    bool strongBreakAbove = close > activeRangeTop + breakoutBuffer * 2.0

    bool strongBreakBelow = close < activeRangeBottom - breakoutBuffer * 2.0

    if strongBreakAbove or strongBreakBelow
        activeRangeExists := false


// ============================================================================
// SECTION 26: CREATE NEW RANGE
// ============================================================================

if not activeRangeExists and detectedValid

    activeRangeExists := true

    activeRangeTop := detectedTop

    activeRangeBottom := detectedBottom

    activeRangeStartBar := bar_index - detectedLength + 1

    activeRangeStatus := STATUS_CONSOLIDATION

    activeRangeBreakoutBar := na

    activeRangeScanLength := detectedLength

    newRangeCreated := true


// ============================================================================
// SECTION 27: RANGE LEVELS
// ============================================================================

float rangeMidpoint = na
float rangeQ25 = na
float rangeQ75 = na

if activeRangeExists

    rangeMidpoint := (activeRangeTop + activeRangeBottom) / 2.0

    rangeQ25 := activeRangeBottom + (activeRangeTop - activeRangeBottom) * 0.25

    rangeQ75 := activeRangeBottom + (activeRangeTop - activeRangeBottom) * 0.75


// ============================================================================
// SECTION 28: BREAKOUT SETUP STATE
// ============================================================================

var bool bullBreakoutArmed = false
var bool bearBreakoutArmed = false

var int bullBreakoutBar = na
var int bearBreakoutBar = na

var float bullBreakoutLevel = na
var float bearBreakoutLevel = na

var float bullRetestLow = na
var float bearRetestHigh = na

var bool bullRetestSeen = false
var bool bearRetestSeen = false


// ============================================================================
// SECTION 29: ARM BULL BREAKOUT
// ============================================================================

if sourceBullBreakout and allowBreakout

    bullBreakoutArmed := true

    bullBreakoutBar := bar_index

    bullBreakoutLevel := activeRangeTop

    bullRetestLow := na

    bullRetestSeen := false

    bearBreakoutArmed := false

    bearRetestSeen := false


// ============================================================================
// SECTION 30: ARM BEAR BREAKOUT
// ============================================================================

if sourceBearBreakout and allowBreakout

    bearBreakoutArmed := true

    bearBreakoutBar := bar_index

    bearBreakoutLevel := activeRangeBottom

    bearRetestHigh := na

    bearRetestSeen := false

    bullBreakoutArmed := false

    bullRetestSeen := false


// ============================================================================
// SECTION 31: BREAKOUT SETUP EXPIRATION
// ============================================================================

bool bullBreakoutExpired = bullBreakoutArmed and not na(bullBreakoutBar) and bar_index - bullBreakoutBar > breakoutSetupLifetime

bool bearBreakoutExpired = bearBreakoutArmed and not na(bearBreakoutBar) and bar_index - bearBreakoutBar > breakoutSetupLifetime


if bullBreakoutExpired

    bullBreakoutArmed := false
    bullRetestSeen := false


if bearBreakoutExpired

    bearBreakoutArmed := false
    bearRetestSeen := false


// ============================================================================
// SECTION 32: BREAKOUT RETEST DETECTION
// ============================================================================

float bullRetestUpper = na

float bearRetestLower = na


if bullBreakoutArmed and not na(bullBreakoutLevel)
    bullRetestUpper := bullBreakoutLevel + atrValue * breakoutRetestToleranceATR


if bearBreakoutArmed and not na(bearBreakoutLevel)
    bearRetestLower := bearBreakoutLevel - atrValue * breakoutRetestToleranceATR


bool bullTouchesRetest = (
     bullBreakoutArmed and
     not na(bullBreakoutBar) and
     bar_index > bullBreakoutBar and
     low <= bullRetestUpper
)


bool bearTouchesRetest = (
     bearBreakoutArmed and
     not na(bearBreakoutBar) and
     bar_index > bearBreakoutBar and
     high >= bearRetestLower
)


if bullTouchesRetest

    bullRetestSeen := true

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


if bearTouchesRetest

    bearRetestSeen := true

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


// ============================================================================
// SECTION 33: DEVIATION SETUP STATE
// ============================================================================

var bool bullDeviationArmed = false
var bool bearDeviationArmed = false

var int bullDeviationBar = na
var int bearDeviationBar = na

var float bullDeviationStopSource = na
var float bearDeviationStopSource = na

var float bullDeviationTarget = na
var float bearDeviationTarget = na

var float bullDeviationQ25 = na
var float bearDeviationQ75 = na


// ============================================================================
// SECTION 34: ARM BULL DEVIATION
// ============================================================================

if sourceBullDeviation and allowDeviation

    bullDeviationArmed := true

    bullDeviationBar := bar_index

    bullDeviationStopSource := low

    bullDeviationTarget := rangeMidpoint

    bullDeviationQ25 := rangeQ25

    bearDeviationArmed := false


// ============================================================================
// SECTION 35: ARM BEAR DEVIATION
// ============================================================================

if sourceBearDeviation and allowDeviation

    bearDeviationArmed := true

    bearDeviationBar := bar_index

    bearDeviationStopSource := high

    bearDeviationTarget := rangeMidpoint

    bearDeviationQ75 := rangeQ75

    bullDeviationArmed := false


// ============================================================================
// SECTION 36: DEVIATION EXPIRATION
// ============================================================================

bool bullDeviationExpired = bullDeviationArmed and not na(bullDeviationBar) and bar_index - bullDeviationBar > deviationConfirmationWindow

bool bearDeviationExpired = bearDeviationArmed and not na(bearDeviationBar) and bar_index - bearDeviationBar > deviationConfirmationWindow


if bullDeviationExpired
    bullDeviationArmed := false


if bearDeviationExpired
    bearDeviationArmed := false


// ============================================================================
// SECTION 37: CANDLE QUALITY
// ============================================================================

float candleRange = high - low

float candleBody = math.abs(close - open)

float bodyRatio = 0.0

if candleRange > 0
    bodyRatio := candleBody / candleRange


bool bullishCandle = close > open

bool bearishCandle = close < open


// ============================================================================
// SECTION 38: BREAKOUT CONFIRMATION
// ============================================================================

bool bullBreakoutBodyOK = bodyRatio >= breakoutMinimumBodyRatio

bool bearBreakoutBodyOK = bodyRatio >= breakoutMinimumBodyRatio


bool bullPreviousBreakOK = (
     not breakoutRequirePreviousBreak or
     close > high[1]
)


bool bearPreviousBreakOK = (
     not breakoutRequirePreviousBreak or
     close < low[1]
)


bool rawBullBreakoutEntry = (
     bullBreakoutArmed and
     bullRetestSeen and
     bullishCandle and
     bullBreakoutBodyOK and
     bullPreviousBreakOK and
     not na(bullBreakoutLevel) and
     close > bullBreakoutLevel and
     barstate.isconfirmed
)


bool rawBearBreakoutEntry = (
     bearBreakoutArmed and
     bearRetestSeen and
     bearishCandle and
     bearBreakoutBodyOK and
     bearPreviousBreakOK and
     not na(bearBreakoutLevel) and
     close < bearBreakoutLevel and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 39: BREAKOUT RISK
// ============================================================================

float bullBreakoutStop = na

float bearBreakoutStop = na


if rawBullBreakoutEntry and not na(bullRetestLow)
    bullBreakoutStop := bullRetestLow - atrValue * breakoutStopBufferATR


if rawBearBreakoutEntry and not na(bearRetestHigh)
    bearBreakoutStop := bearRetestHigh + atrValue * breakoutStopBufferATR


float bullBreakoutRisk = na

float bearBreakoutRisk = na


if not na(bullBreakoutStop)
    bullBreakoutRisk := close - bullBreakoutStop


if not na(bearBreakoutStop)
    bearBreakoutRisk := bearBreakoutStop - close


bool bullBreakoutRiskOK = (
     not na(bullBreakoutRisk) and
     bullBreakoutRisk >= breakoutMinimumRisk and
     bullBreakoutRisk <= breakoutMaximumRisk
)


bool bearBreakoutRiskOK = (
     not na(bearBreakoutRisk) and
     bearBreakoutRisk >= breakoutMinimumRisk and
     bearBreakoutRisk <= breakoutMaximumRisk
)


// ============================================================================
// SECTION 40: DEVIATION CONFIRMATION
// ============================================================================

bool bullDeviationBodyOK = bodyRatio >= deviationMinimumBodyRatio

bool bearDeviationBodyOK = bodyRatio >= deviationMinimumBodyRatio


bool bullDeviationPreviousBreakOK = (
     not deviationRequirePreviousBreak or
     close > high[1]
)


bool bearDeviationPreviousBreakOK = (
     not deviationRequirePreviousBreak or
     close < low[1]
)


bool bullQuartileOK = (
     not deviationRequireQuartileReclaim or
     (
         not na(bullDeviationQ25) and
         close > bullDeviationQ25
     )
)


bool bearQuartileOK = (
     not deviationRequireQuartileReclaim or
     (
         not na(bearDeviationQ75) and
         close < bearDeviationQ75
     )
)


bool rawBullDeviationEntry = (
     bullDeviationArmed and
     not na(bullDeviationBar) and
     bar_index > bullDeviationBar and
     bullishCandle and
     bullDeviationBodyOK and
     bullDeviationPreviousBreakOK and
     bullQuartileOK and
     barstate.isconfirmed
)


bool rawBearDeviationEntry = (
     bearDeviationArmed and
     not na(bearDeviationBar) and
     bar_index > bearDeviationBar and
     bearishCandle and
     bearDeviationBodyOK and
     bearDeviationPreviousBreakOK and
     bearQuartileOK and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 41: DEVIATION RISK / REWARD
// ============================================================================

float bullDeviationStop = na

float bearDeviationStop = na


if rawBullDeviationEntry and not na(bullDeviationStopSource)
    bullDeviationStop := bullDeviationStopSource - atrValue * deviationStopBufferATR


if rawBearDeviationEntry and not na(bearDeviationStopSource)
    bearDeviationStop := bearDeviationStopSource + atrValue * deviationStopBufferATR


float bullDeviationRisk = na

float bearDeviationRisk = na

float bullDeviationReward = na

float bearDeviationReward = na


if not na(bullDeviationStop)
    bullDeviationRisk := close - bullDeviationStop


if not na(bearDeviationStop)
    bearDeviationRisk := bearDeviationStop - close


if not na(bullDeviationTarget)
    bullDeviationReward := bullDeviationTarget - close


if not na(bearDeviationTarget)
    bearDeviationReward := close - bearDeviationTarget


float bullDeviationRR = na

float bearDeviationRR = na


if not na(bullDeviationRisk) and bullDeviationRisk > 0
    bullDeviationRR := bullDeviationReward / bullDeviationRisk


if not na(bearDeviationRisk) and bearDeviationRisk > 0
    bearDeviationRR := bearDeviationReward / bearDeviationRisk


bool bullDeviationRiskOK = (
     not na(bullDeviationRisk) and
     bullDeviationRisk > 0 and
     bullDeviationRisk <= maximumDeviationRisk and
     not na(bullDeviationReward) and
     bullDeviationReward > 0 and
     not na(bullDeviationRR) and
     bullDeviationRR >= minimumDeviationRR
)


bool bearDeviationRiskOK = (
     not na(bearDeviationRisk) and
     bearDeviationRisk > 0 and
     bearDeviationRisk <= maximumDeviationRisk and
     not na(bearDeviationReward) and
     bearDeviationReward > 0 and
     not na(bearDeviationRR) and
     bearDeviationRR >= minimumDeviationRR
)


// ============================================================================
// SECTION 42: 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 43: COOLDOWN
// ============================================================================

var int lastExitBar = na

if positionJustClosed
    lastExitBar := bar_index


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


// ============================================================================
// SECTION 44: ENTRY APPROVAL
// ============================================================================

bool tradeLimitOK = tradesToday < maximumTradesPerDay


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


// ============================================================================
// SECTION 45: FINAL SETUP CANDIDATES
// ============================================================================

bool breakoutLongCandidate = (
     commonEntryApproval and
     allowLong and
     allowBreakout and
     rawBullBreakoutEntry and
     bullBreakoutRiskOK
)


bool breakoutShortCandidate = (
     commonEntryApproval and
     allowShort and
     allowBreakout and
     rawBearBreakoutEntry and
     bearBreakoutRiskOK
)


bool deviationLongCandidate = (
     commonEntryApproval and
     allowLong and
     allowDeviation and
     rawBullDeviationEntry and
     bullDeviationRiskOK
)


bool deviationShortCandidate = (
     commonEntryApproval and
     allowShort and
     allowDeviation and
     rawBearDeviationEntry and
     bearDeviationRiskOK
)


// ============================================================================
// SECTION 46: PRIORITY
// ============================================================================
//
// If a deviation and breakout somehow coexist,
// deviation gets priority because it represents a failed breakout event.
//
// ============================================================================

bool finalLongDeviation = deviationLongCandidate

bool finalShortDeviation = deviationShortCandidate


bool finalLongBreakout = (
     breakoutLongCandidate and
     not finalLongDeviation and
     not finalShortDeviation
)


bool finalShortBreakout = (
     breakoutShortCandidate and
     not finalLongDeviation and
     not finalShortDeviation
)


bool finalLongSignal = (
     finalLongDeviation or
     finalLongBreakout
)


bool finalShortSignal = (
     finalShortDeviation or
     finalShortBreakout
)


bool conflict = (
     finalLongSignal and
     finalShortSignal
)


finalLongSignal := finalLongSignal and not conflict

finalShortSignal := finalShortSignal and not conflict


// ============================================================================
// SECTION 47: PENDING EXIT STATE
// ============================================================================

var float pendingStop = na

var float pendingTarget = na

var string pendingSetupType = ""


// ============================================================================
// SECTION 48: LONG ENTRY
// ============================================================================

if finalLongSignal

    if finalLongDeviation

        pendingStop := bullDeviationStop

        pendingTarget := bullDeviationTarget

        pendingSetupType := "DEV"


        strategy.entry(
             "Long",
             strategy.long,
             qty=3,
             comment="Bull Deviation",
             alert_message="ENTRY_LONG_DEVIATION_CNPS28_V2"
        )


        bullDeviationArmed := false


    else

        pendingStop := bullBreakoutStop

        pendingTarget := close + bullBreakoutRisk * breakoutRewardRisk

        pendingSetupType := "BO"


        strategy.entry(
             "Long",
             strategy.long,
             qty=3,
             comment="Bull Breakout Retest",
             alert_message="ENTRY_LONG_BREAKOUT_CNPS28_V2"
        )


        bullBreakoutArmed := false

        bullRetestSeen := false


    tradesToday += 1


// ============================================================================
// SECTION 49: SHORT ENTRY
// ============================================================================

if finalShortSignal

    if finalShortDeviation

        pendingStop := bearDeviationStop

        pendingTarget := bearDeviationTarget

        pendingSetupType := "DEV"


        strategy.entry(
             "Short",
             strategy.short,
             qty=3,
             comment="Bear Deviation",
             alert_message="ENTRY_SHORT_DEVIATION_CNPS28_V2"
        )


        bearDeviationArmed := false


    else

        pendingStop := bearBreakoutStop

        pendingTarget := close - bearBreakoutRisk * breakoutRewardRisk

        pendingSetupType := "BO"


        strategy.entry(
             "Short",
             strategy.short,
             qty=3,
             comment="Bear Breakout Retest",
             alert_message="ENTRY_SHORT_BREAKOUT_CNPS28_V2"
        )


        bearBreakoutArmed := false

        bearRetestSeen := false


    tradesToday += 1


// ============================================================================
// SECTION 50: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeLongSL = na
var float activeLongTP = na

var float activeShortSL = na
var float activeShortTP = na

var string activeSetupType = ""


// ============================================================================
// SECTION 51: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeLongSL := pendingStop

    activeLongTP := pendingTarget

    activeShortSL := na

    activeShortTP := na

    activeSetupType := pendingSetupType

    pendingStop := na

    pendingTarget := na

    pendingSetupType := ""



// ============================================================================
// SECTION 52: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeShortSL := pendingStop

    activeShortTP := pendingTarget

    activeLongSL := na

    activeLongTP := na

    activeSetupType := pendingSetupType

    pendingStop := na

    pendingTarget := na

    pendingSetupType := ""



// ============================================================================
// SECTION 53: LONG EXIT
// ============================================================================

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_CNPS28_V2",
         alert_loss="EXIT_LONG_SL_CNPS28_V2",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 54: SHORT EXIT
// ============================================================================

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_CNPS28_V2",
         alert_loss="EXIT_SHORT_SL_CNPS28_V2",
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 55: POSITION CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeLongSL := na

    activeLongTP := na

    activeShortSL := na

    activeShortTP := na

    activeSetupType := ""



// ============================================================================
// SECTION 56: SESSION END
// ============================================================================

bool finalSessionBar = (
     currentMinute < sessionEndTotal and
     currentCloseMinute >= sessionEndTotal
)


if finalSessionBar

    bullBreakoutArmed := false

    bearBreakoutArmed := false

    bullDeviationArmed := false

    bearDeviationArmed := false


    if strategy.position_size != 0

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


// ============================================================================
// SECTION 57: RANGE DISPLAY
// ============================================================================

plot(
     showRangeLevels and activeRangeExists ? activeRangeTop : na,
     title="Range Top",
     color=color.gray,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showRangeLevels and activeRangeExists ? rangeQ75 : na,
     title="Range Q75",
     color=color.new(color.purple, 55),
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showRangeLevels and activeRangeExists ? rangeMidpoint : na,
     title="Range EQ",
     color=color.blue,
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showRangeLevels and activeRangeExists ? rangeQ25 : na,
     title="Range Q25",
     color=color.new(color.purple, 55),
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showRangeLevels and activeRangeExists ? activeRangeBottom : na,
     title="Range Bottom",
     color=color.gray,
     linewidth=2,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 58: ARMED SETUP LEVELS
// ============================================================================

plot(
     showSetupLevels and bullBreakoutArmed ? bullBreakoutLevel : na,
     title="Bull BO Level",
     color=color.green,
     linewidth=1,
     style=plot.style_linebr
)


plot(
     showSetupLevels and bearBreakoutArmed ? bearBreakoutLevel : na,
     title="Bear BO Level",
     color=color.red,
     linewidth=1,
     style=plot.style_linebr
)


// ============================================================================
// SECTION 59: 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 60: SIGNAL MARKERS
// ============================================================================

plotshape(
     showSignals and sourceBullBreakout,
     title="Source Bull Breakout",
     style=shape.circle,
     location=location.belowbar,
     color=color.green,
     size=size.tiny,
     text="BO"
)


plotshape(
     showSignals and sourceBearBreakout,
     title="Source Bear Breakout",
     style=shape.circle,
     location=location.abovebar,
     color=color.red,
     size=size.tiny,
     text="BO"
)


plotshape(
     showSignals and sourceBullDeviation,
     title="Source Bull Deviation",
     style=shape.diamond,
     location=location.belowbar,
     color=color.lime,
     size=size.tiny,
     text="D"
)


plotshape(
     showSignals and sourceBearDeviation,
     title="Source Bear Deviation",
     style=shape.diamond,
     location=location.abovebar,
     color=color.orange,
     size=size.tiny,
     text="D"
)


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


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


// ============================================================================
// SECTION 61: DIAGNOSTIC COUNTERS
// ============================================================================

var int rangeCount = 0

var int bullBreakoutCount = 0

var int bearBreakoutCount = 0

var int bullDeviationCount = 0

var int bearDeviationCount = 0

var int breakoutEntryCount = 0

var int deviationEntryCount = 0


if newRangeCreated
    rangeCount += 1


if sourceBullBreakout
    bullBreakoutCount += 1


if sourceBearBreakout
    bearBreakoutCount += 1


if sourceBullDeviation
    bullDeviationCount += 1


if sourceBearDeviation
    bearDeviationCount += 1


if finalLongBreakout or finalShortBreakout
    breakoutEntryCount += 1


if finalLongDeviation or finalShortDeviation
    deviationEntryCount += 1


// ============================================================================
// SECTION 62: DIAGNOSTICS
// ============================================================================

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


if showDiagnostics and barstate.islast

    string rangeText = "NONE"

    if activeRangeExists

        if activeRangeStatus == STATUS_CONSOLIDATION
            rangeText := "RANGE"

        else if activeRangeStatus == STATUS_BREAKOUT_BULL
            rangeText := "BO BULL"

        else if activeRangeStatus == STATUS_BREAKOUT_BEAR
            rangeText := "BO BEAR"

        else if activeRangeStatus == STATUS_DEVIATION_BULL
            rangeText := "DEV BULL"

        else if activeRangeStatus == STATUS_DEVIATION_BEAR
            rangeText := "DEV BEAR"


    string positionText = "FLAT"

    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


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


    table.cell(diagnostics, 0, 1, "Range")
    table.cell(diagnostics, 1, 1, rangeText)


    table.cell(diagnostics, 0, 2, "Detected Len")
    table.cell(diagnostics, 1, 2, str.tostring(detectedLength))


    table.cell(diagnostics, 0, 3, "Top")
    table.cell(diagnostics, 1, 3, str.tostring(activeRangeTop, format.mintick))


    table.cell(diagnostics, 0, 4, "EQ")
    table.cell(diagnostics, 1, 4, str.tostring(rangeMidpoint, format.mintick))


    table.cell(diagnostics, 0, 5, "Bottom")
    table.cell(diagnostics, 1, 5, str.tostring(activeRangeBottom, format.mintick))


    table.cell(diagnostics, 0, 6, "Compression")
    table.cell(diagnostics, 1, 6, str.tostring(detectedRank, "0.0"))


    table.cell(diagnostics, 0, 7, "Rotation")
    table.cell(diagnostics, 1, 7, str.tostring(detectedRotation, "0.00"))


    table.cell(diagnostics, 0, 8, "Containment")
    table.cell(diagnostics, 1, 8, str.tostring(detectedContainment, "0.0") + "%")


    table.cell(diagnostics, 0, 9, "Ranges")
    table.cell(diagnostics, 1, 9, str.tostring(rangeCount))


    table.cell(diagnostics, 0, 10, "Source BO L/S")
    table.cell(diagnostics, 1, 10, str.tostring(bullBreakoutCount) + " / " + str.tostring(bearBreakoutCount))


    table.cell(diagnostics, 0, 11, "Source DEV L/S")
    table.cell(diagnostics, 1, 11, str.tostring(bullDeviationCount) + " / " + str.tostring(bearDeviationCount))


    table.cell(diagnostics, 0, 12, "BO Entries")
    table.cell(diagnostics, 1, 12, str.tostring(breakoutEntryCount))


    table.cell(diagnostics, 0, 13, "DEV Entries")
    table.cell(diagnostics, 1, 13, str.tostring(deviationEntryCount))


    table.cell(diagnostics, 0, 14, "Bull BO Armed")
    table.cell(diagnostics, 1, 14, bullBreakoutArmed ? "YES" : "NO")


    table.cell(diagnostics, 0, 15, "Bear BO Armed")
    table.cell(diagnostics, 1, 15, bearBreakoutArmed ? "YES" : "NO")


    table.cell(diagnostics, 0, 16, "Active Setup")
    table.cell(diagnostics, 1, 16, activeSetupType)


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


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