//@version=6
strategy(
     title="CNPS32 V2 TRENDLINE BREAK PRO CLEAN",
     shorttitle="CNPS32 V2 TLB",
     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_lines_count=100,
     max_labels_count=100,
     max_bars_back=5000
)


// ============================================================================
// CNPS32 V2 TRENDLINE BREAK PRO CLEAN
//
// CORE SOURCE LOGIC:
//
// RESISTANCE
// Two consecutive LOWER confirmed pivot highs
// -> descending resistance trendline
//
// SUPPORT
// Two consecutive HIGHER confirmed pivot lows
// -> ascending support trendline
//
// BREAK:
// Close beyond trendline + ATR buffer
// -> consume trendline
// -> apply confirmation filters
// -> entry
//
// V2 CHANGES:
//
// 1. Reverse on opposite signal = OFF by default.
// 2. Maximum 3 trades per day.
// 3. 5-bar cooldown after flat exit.
// 4. Only ONE current resistance line.
// 5. Only ONE current support line.
// 6. Broken lines remain briefly, then are deleted.
// 7. Expired trendlines are deleted.
// 8. Cleaner chart visuals.
//
// SOURCE-LIKE MANAGEMENT DEFAULT:
// ATR Stop = 1.5 ATR
// TP = 2R
// Break Even = 1R
// Trail starts = 1.5R
// Trail distance = 2 ATR
//
// CNPS:
// 100M VND
// 3 contracts
// 10,000 VND commission / contract
// 3 ticks slippage
// ============================================================================


// ============================================================================
// SECTION 1: TRENDLINE DETECTION
// ============================================================================

pivotLength = input.int(
     5,
     title="Pivot Length",
     minval=2,
     maxval=100,
     group="Trendline Detection"
)

minimumPivotSpan = input.int(
     8,
     title="Minimum Bars Between Pivots",
     minval=1,
     maxval=500,
     group="Trendline Detection"
)

maximumLineAge = input.int(
     150,
     title="Maximum Line Age",
     minval=10,
     maxval=2000,
     group="Trendline Detection"
)

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

breakBufferATR = input.float(
     0.10,
     title="Break Buffer x ATR",
     minval=0.0,
     maxval=5.0,
     step=0.05,
     group="Trendline Detection"
)


// ============================================================================
// SECTION 2: VISUAL TRENDLINE LIFETIME
// ============================================================================

showTrendlines = input.bool(
     true,
     title="Show Current Trendlines?",
     group="Trendline Visuals"
)

showBrokenTrendline = input.bool(
     true,
     title="Briefly Show Broken Trendline?",
     group="Trendline Visuals"
)

brokenLineDisplayBars = input.int(
     2,
     title="Broken Line Display Bars",
     minval=0,
     maxval=20,
     group="Trendline Visuals"
)


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

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

bool enableLong = tradeDirection == "Long" or tradeDirection == "Both"

bool enableShort = tradeDirection == "Short" or tradeDirection == "Both"

reverseOnOppositeSignal = input.bool(
     false,
     title="Reverse On Opposite Signal?",
     group="Direction"
)


// ============================================================================
// SECTION 4: MOMENTUM FILTER
// ============================================================================

useMomentum = input.bool(
     true,
     title="Momentum Filter",
     group="Confirmation Filters"
)

aoFastLength = input.int(
     5,
     title="AO Fast",
     minval=1,
     group="Confirmation Filters"
)

aoSlowLength = input.int(
     34,
     title="AO Slow",
     minval=2,
     group="Confirmation Filters"
)

aoSmoothing = input.int(
     5,
     title="AO Smoothing",
     minval=1,
     group="Confirmation Filters"
)


// ============================================================================
// SECTION 5: EMA FILTER
// ============================================================================

useEMAFilter = input.bool(
     false,
     title="Trend Filter EMA",
     group="Confirmation Filters"
)

emaLength = input.int(
     100,
     title="EMA Length",
     minval=1,
     group="Confirmation Filters"
)


// ============================================================================
// SECTION 6: ADX FILTER
// ============================================================================

useADXFilter = input.bool(
     false,
     title="ADX Filter",
     group="Confirmation Filters"
)

adxThreshold = input.float(
     18.0,
     title="ADX Threshold",
     minval=1.0,
     step=1.0,
     group="Confirmation Filters"
)


// ============================================================================
// SECTION 7: VOLUME FILTER
// ============================================================================

useVolumeFilter = input.bool(
     false,
     title="Volume Filter",
     group="Confirmation Filters"
)

volumeMultiplier = input.float(
     1.20,
     title="Volume > SMA20 x",
     minval=0.1,
     step=0.1,
     group="Confirmation Filters"
)


// ============================================================================
// SECTION 8: EXIT MODE
// ============================================================================

exitMode = input.string(
     "Source Dynamic",
     title="Exit Mode",
     options=[
         "Source Dynamic",
         "Fixed Points"
     ],
     group="Risk Management"
)


// ============================================================================
// SECTION 9: SOURCE STOP
// ============================================================================

sourceStopMode = input.string(
     "ATR",
     title="Source Stop Mode",
     options=["ATR", "Swing"],
     group="Risk Management"
)

atrStopMultiplier = input.float(
     1.50,
     title="ATR Stop Multiplier",
     minval=0.1,
     step=0.1,
     group="Risk Management"
)

swingLookback = input.int(
     10,
     title="Swing Lookback",
     minval=2,
     maxval=100,
     group="Risk Management"
)

swingStopPaddingATR = input.float(
     0.25,
     title="Swing Stop Padding x ATR",
     minval=0.0,
     step=0.05,
     group="Risk Management"
)


// ============================================================================
// SECTION 10: TARGET
// ============================================================================

useTakeProfit = input.bool(
     true,
     title="Use Take Profit?",
     group="Risk Management"
)

rewardRiskTarget = input.float(
     2.0,
     title="Take Profit R",
     minval=0.1,
     maxval=10.0,
     step=0.1,
     group="Risk Management"
)


// ============================================================================
// SECTION 11: BREAK EVEN
// ============================================================================

useBreakEven = input.bool(
     true,
     title="Use Break Even?",
     group="Risk Management"
)

breakEvenTriggerR = input.float(
     1.0,
     title="Break Even Trigger R",
     minval=0.1,
     maxval=10.0,
     step=0.1,
     group="Risk Management"
)

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


// ============================================================================
// SECTION 12: TRAILING STOP
// ============================================================================

useTrailingStop = input.bool(
     true,
     title="Use ATR Trailing Stop?",
     group="Risk Management"
)

trailStartR = input.float(
     1.5,
     title="Trail Starts At R",
     minval=0.1,
     maxval=10.0,
     step=0.1,
     group="Risk Management"
)

trailATRMultiplier = input.float(
     2.0,
     title="Trail Distance x ATR",
     minval=0.1,
     maxval=10.0,
     step=0.1,
     group="Risk Management"
)


// ============================================================================
// SECTION 13: FIXED POINT EXIT
// ============================================================================

fixedStopLossPoints = input.float(
     5.0,
     title="Fixed Stop Loss",
     minval=0.5,
     step=0.5,
     group="Fixed Exit"
)

fixedTakeProfitPoints = input.float(
     15.0,
     title="Fixed Take Profit",
     minval=0.5,
     step=0.5,
     group="Fixed Exit"
)


// ============================================================================
// SECTION 14: TIME EXIT
// ============================================================================

maximumBarsInTrade = input.int(
     0,
     title="Maximum Bars In Trade (0 = Off)",
     minval=0,
     maxval=5000,
     group="Risk Management"
)


// ============================================================================
// SECTION 15: TRADE CONTROL
// ============================================================================

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

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


// ============================================================================
// SECTION 16: SESSION
// ============================================================================

strategyTimezone = input.string(
     "Asia/Ho_Chi_Minh",
     title="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 17: ALERTS
// ============================================================================

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


// ============================================================================
// SECTION 18: DISPLAY
// ============================================================================

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

showEMA = input.bool(
     false,
     title="Show Trend EMA?",
     group="Display"
)

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

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


// ============================================================================
// SECTION 19: INDICATORS
// ============================================================================

float atrValue = ta.atr(atrLength)

float emaTrend = ta.ema(close, emaLength)

float aoRaw = ta.sma(hl2, aoFastLength) - ta.ema(hl2, aoSlowLength)

float aoValue = ta.ema(aoRaw, aoSmoothing)

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

float volumeAverage = ta.sma(volume, 20)

float swingLow = ta.lowest(low, swingLookback)

float swingHigh = ta.highest(high, swingLookback)


// ============================================================================
// SECTION 20: CONFIRMED PIVOTS
// ============================================================================

float pivotHigh = ta.pivothigh(
     high,
     pivotLength,
     pivotLength
)

float pivotLow = ta.pivotlow(
     low,
     pivotLength,
     pivotLength
)


// ============================================================================
// SECTION 21: RESISTANCE STATE
// ============================================================================

var int resistancePivot1Bar = na

var float resistancePivot1Price = na

var int resistancePivot2Bar = na

var float resistancePivot2Price = na

var float resistanceSlope = na

var bool resistanceValid = false

var bool resistanceBroken = false

var int resistanceBrokenBar = na

var line resistanceLine = na


// ============================================================================
// SECTION 22: SUPPORT STATE
// ============================================================================

var int supportPivot1Bar = na

var float supportPivot1Price = na

var int supportPivot2Bar = na

var float supportPivot2Price = na

var float supportSlope = na

var bool supportValid = false

var bool supportBroken = false

var int supportBrokenBar = na

var line supportLine = na


// ============================================================================
// SECTION 23: BUILD RESISTANCE
// ============================================================================
//
// CRITICAL V2 VISUAL CHANGE:
//
// Always delete the previous resistance drawing when a new pivot high
// arrives.
//
// Therefore the chart can never accumulate historical resistance lines.
//
// ============================================================================

if not na(pivotHigh)

    if not na(resistanceLine)
        line.delete(resistanceLine)

    resistanceLine := na


    resistancePivot1Bar := resistancePivot2Bar

    resistancePivot1Price := resistancePivot2Price

    resistancePivot2Bar := bar_index - pivotLength

    resistancePivot2Price := pivotHigh


    resistanceValid := (
         not na(resistancePivot1Price) and
         not na(resistancePivot1Bar) and
         resistancePivot2Price < resistancePivot1Price and
         resistancePivot2Bar - resistancePivot1Bar >= minimumPivotSpan
    )


    resistanceBroken := false

    resistanceBrokenBar := na


    if resistanceValid

        resistanceSlope := (resistancePivot2Price - resistancePivot1Price) / float(resistancePivot2Bar - resistancePivot1Bar)


        bool pierced = false


        for i = 0 to pivotLength

            float historicalLevel = resistancePivot2Price + resistanceSlope * float((bar_index - i) - resistancePivot2Bar)


            if close[i] > historicalLevel
                pierced := true


        resistanceBroken := pierced


        if pierced
            resistanceBrokenBar := bar_index


        if showTrendlines

            resistanceLine := line.new(
                 resistancePivot1Bar,
                 resistancePivot1Price,
                 resistancePivot2Bar,
                 resistancePivot2Price,
                 extend=extend.right,
                 color=pierced ? color.gray : color.red,
                 width=2
            )


// ============================================================================
// SECTION 24: BUILD SUPPORT
// ============================================================================

if not na(pivotLow)

    if not na(supportLine)
        line.delete(supportLine)

    supportLine := na


    supportPivot1Bar := supportPivot2Bar

    supportPivot1Price := supportPivot2Price

    supportPivot2Bar := bar_index - pivotLength

    supportPivot2Price := pivotLow


    supportValid := (
         not na(supportPivot1Price) and
         not na(supportPivot1Bar) and
         supportPivot2Price > supportPivot1Price and
         supportPivot2Bar - supportPivot1Bar >= minimumPivotSpan
    )


    supportBroken := false

    supportBrokenBar := na


    if supportValid

        supportSlope := (supportPivot2Price - supportPivot1Price) / float(supportPivot2Bar - supportPivot1Bar)


        bool pierced = false


        for i = 0 to pivotLength

            float historicalLevel = supportPivot2Price + supportSlope * float((bar_index - i) - supportPivot2Bar)


            if close[i] < historicalLevel
                pierced := true


        supportBroken := pierced


        if pierced
            supportBrokenBar := bar_index


        if showTrendlines

            supportLine := line.new(
                 supportPivot1Bar,
                 supportPivot1Price,
                 supportPivot2Bar,
                 supportPivot2Price,
                 extend=extend.right,
                 color=pierced ? color.gray : color.teal,
                 width=2
            )


// ============================================================================
// SECTION 25: CURRENT TRENDLINE LEVELS
// ============================================================================

float resistanceLevel = na

float supportLevel = na


if resistanceValid and not na(resistanceSlope)

    resistanceLevel := resistancePivot2Price + resistanceSlope * float(bar_index - resistancePivot2Bar)


if supportValid and not na(supportSlope)

    supportLevel := supportPivot2Price + supportSlope * float(bar_index - supportPivot2Bar)


// ============================================================================
// SECTION 26: LINE FRESHNESS
// ============================================================================

bool resistanceFresh = (
     not na(resistancePivot2Bar) and
     bar_index - resistancePivot2Bar <= maximumLineAge
)


bool supportFresh = (
     not na(supportPivot2Bar) and
     bar_index - supportPivot2Bar <= maximumLineAge
)


// ============================================================================
// SECTION 27: DELETE EXPIRED LIVE TRENDLINES
// ============================================================================

if (
     resistanceValid and
     not resistanceBroken and
     not resistanceFresh
)

    if not na(resistanceLine)
        line.delete(resistanceLine)

    resistanceLine := na

    resistanceValid := false


if (
     supportValid and
     not supportBroken and
     not supportFresh
)

    if not na(supportLine)
        line.delete(supportLine)

    supportLine := na

    supportValid := false


// ============================================================================
// SECTION 28: RAW BREAK DETECTION
// ============================================================================

bool rawBreakUp = (
     resistanceValid and
     not resistanceBroken and
     resistanceFresh and
     not na(resistanceLevel) and
     close > resistanceLevel + atrValue * breakBufferATR and
     barstate.isconfirmed
)


bool rawBreakDown = (
     supportValid and
     not supportBroken and
     supportFresh and
     not na(supportLevel) and
     close < supportLevel - atrValue * breakBufferATR and
     barstate.isconfirmed
)


// ============================================================================
// SECTION 29: CONSUME RESISTANCE BREAK
// ============================================================================

if rawBreakUp

    resistanceBroken := true

    resistanceBrokenBar := bar_index


    if not na(resistanceLine)

        line.set_extend(
             resistanceLine,
             extend.none
        )

        line.set_x2(
             resistanceLine,
             bar_index
        )

        line.set_y2(
             resistanceLine,
             resistanceLevel
        )

        line.set_color(
             resistanceLine,
             color.gray
        )

        line.set_style(
             resistanceLine,
             line.style_dotted
        )


// ============================================================================
// SECTION 30: CONSUME SUPPORT BREAK
// ============================================================================

if rawBreakDown

    supportBroken := true

    supportBrokenBar := bar_index


    if not na(supportLine)

        line.set_extend(
             supportLine,
             extend.none
        )

        line.set_x2(
             supportLine,
             bar_index
        )

        line.set_y2(
             supportLine,
             supportLevel
        )

        line.set_color(
             supportLine,
             color.gray
        )

        line.set_style(
             supportLine,
             line.style_dotted
        )


// ============================================================================
// SECTION 31: DELETE BROKEN RESISTANCE VISUAL
// ============================================================================

if (
     resistanceBroken and
     not na(resistanceBrokenBar) and
     not na(resistanceLine)
)

    bool removeResistanceVisual = false


    if not showBrokenTrendline

        removeResistanceVisual := true


    else if bar_index - resistanceBrokenBar >= brokenLineDisplayBars

        removeResistanceVisual := true


    if removeResistanceVisual

        line.delete(resistanceLine)

        resistanceLine := na


// ============================================================================
// SECTION 32: DELETE BROKEN SUPPORT VISUAL
// ============================================================================

if (
     supportBroken and
     not na(supportBrokenBar) and
     not na(supportLine)
)

    bool removeSupportVisual = false


    if not showBrokenTrendline

        removeSupportVisual := true


    else if bar_index - supportBrokenBar >= brokenLineDisplayBars

        removeSupportVisual := true


    if removeSupportVisual

        line.delete(supportLine)

        supportLine := na


// ============================================================================
// SECTION 33: MOMENTUM FILTER
// ============================================================================

bool longMomentumOK = (
     not useMomentum or
     aoValue > aoValue[1]
)


bool shortMomentumOK = (
     not useMomentum or
     aoValue < aoValue[1]
)


// ============================================================================
// SECTION 34: EMA FILTER
// ============================================================================

bool longEMAOK = (
     not useEMAFilter or
     close > emaTrend
)


bool shortEMAOK = (
     not useEMAFilter or
     close < emaTrend
)


// ============================================================================
// SECTION 35: ADX FILTER
// ============================================================================

bool adxOK = (
     not useADXFilter or
     adxValue > adxThreshold
)


// ============================================================================
// SECTION 36: VOLUME FILTER
// ============================================================================

bool volumeOK = true


if useVolumeFilter

    if not na(volumeAverage)

        volumeOK := volume > volumeAverage * volumeMultiplier


// ============================================================================
// SECTION 37: QUALIFIED SIGNALS
// ============================================================================

bool longSignal = (
     enableLong and
     rawBreakUp and
     longMomentumOK and
     longEMAOK and
     adxOK and
     volumeOK
)


bool shortSignal = (
     enableShort and
     rawBreakDown and
     shortMomentumOK and
     shortEMAOK and
     adxOK and
     volumeOK
)


// ============================================================================
// SECTION 38: SESSION 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 39: 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 40: 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 41: COOLDOWN
// ============================================================================

var int lastExitBar = na


if positionJustClosed
    lastExitBar := bar_index


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


// ============================================================================
// SECTION 42: DAILY LIMIT
// ============================================================================

bool tradeLimitOK = (
     tradesToday < maximumTradesPerDay
)


// ============================================================================
// SECTION 43: POSITION AVAILABILITY
// ============================================================================

bool canLongByPosition = false

bool canShortByPosition = false


if flat

    canLongByPosition := true

    canShortByPosition := true


else if reverseOnOppositeSignal

    if inShort
        canLongByPosition := true

    if inLong
        canShortByPosition := true


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

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


// ============================================================================
// SECTION 45: PRELIMINARY ENTRIES
// ============================================================================

bool longCandidate = (
     commonEntryApproval and
     longSignal and
     canLongByPosition
)


bool shortCandidate = (
     commonEntryApproval and
     shortSignal and
     canShortByPosition
)


// ============================================================================
// SECTION 46: CONFLICT PROTECTION
// ============================================================================

bool conflictingSignals = (
     longCandidate and
     shortCandidate
)


bool finalLongSignal = (
     longCandidate and
     not conflictingSignals
)


bool finalShortSignal = (
     shortCandidate and
     not conflictingSignals
)


// ============================================================================
// SECTION 47: SOURCE RISK ESTIMATE
// ============================================================================

float sourceLongStop = close - atrValue * atrStopMultiplier

float sourceShortStop = close + atrValue * atrStopMultiplier


if sourceStopMode == "Swing"

    sourceLongStop := swingLow - atrValue * swingStopPaddingATR

    sourceShortStop := swingHigh + atrValue * swingStopPaddingATR


float sourceLongRisk = close - sourceLongStop

float sourceShortRisk = sourceShortStop - close


sourceLongRisk := math.max(
     sourceLongRisk,
     atrValue * 0.5
)


sourceShortRisk := math.max(
     sourceShortRisk,
     atrValue * 0.5
)


// ============================================================================
// SECTION 48: PENDING RISK
// ============================================================================

var float pendingRiskDistance = na


// ============================================================================
// SECTION 49: LONG ENTRY
// ============================================================================

if finalLongSignal

    if exitMode == "Source Dynamic"

        pendingRiskDistance := sourceLongRisk


    else

        pendingRiskDistance := fixedStopLossPoints


    strategy.entry(
         "Long",
         strategy.long,
         qty=3,
         comment="TLB L",
         alert_message="ENTRY_LONG_CNPS32_V2"
    )


    tradesToday += 1


// ============================================================================
// SECTION 50: SHORT ENTRY
// ============================================================================

if finalShortSignal

    if exitMode == "Source Dynamic"

        pendingRiskDistance := sourceShortRisk


    else

        pendingRiskDistance := fixedStopLossPoints


    strategy.entry(
         "Short",
         strategy.short,
         qty=3,
         comment="TLB S",
         alert_message="ENTRY_SHORT_CNPS32_V2"
    )


    tradesToday += 1


// ============================================================================
// SECTION 51: ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na

var float activeRiskDistance = na

var float activeStop = na

var float activeTakeProfit = na

var bool breakEvenDone = false

var bool trailingActive = false

var int activeEntryBar = na


// ============================================================================
// SECTION 52: INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := strategy.position_avg_price

    activeEntryBar := bar_index

    activeRiskDistance := pendingRiskDistance


    if (
         na(activeRiskDistance) or
         activeRiskDistance <= 0
    )

        activeRiskDistance := fixedStopLossPoints


    activeStop := activeEntryPrice - activeRiskDistance


    if exitMode == "Source Dynamic"

        if useTakeProfit

            activeTakeProfit := activeEntryPrice + activeRiskDistance * rewardRiskTarget


        else

            activeTakeProfit := na


    else

        activeTakeProfit := activeEntryPrice + fixedTakeProfitPoints


    breakEvenDone := false

    trailingActive := false

    pendingRiskDistance := na


// ============================================================================
// SECTION 53: INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := strategy.position_avg_price

    activeEntryBar := bar_index

    activeRiskDistance := pendingRiskDistance


    if (
         na(activeRiskDistance) or
         activeRiskDistance <= 0
    )

        activeRiskDistance := fixedStopLossPoints


    activeStop := activeEntryPrice + activeRiskDistance


    if exitMode == "Source Dynamic"

        if useTakeProfit

            activeTakeProfit := activeEntryPrice - activeRiskDistance * rewardRiskTarget


        else

            activeTakeProfit := na


    else

        activeTakeProfit := activeEntryPrice - fixedTakeProfitPoints


    breakEvenDone := false

    trailingActive := false

    pendingRiskDistance := na


// ============================================================================
// SECTION 54: LONG DYNAMIC MANAGEMENT
// ============================================================================

if (
     inLong and
     exitMode == "Source Dynamic" and
     not newLongPosition and
     not na(activeEntryPrice) and
     not na(activeRiskDistance)
)

    if (
         useBreakEven and
         not breakEvenDone and
         high >= activeEntryPrice + activeRiskDistance * breakEvenTriggerR
    )

        float breakEvenStop = activeEntryPrice + breakEvenOffsetPoints

        activeStop := math.max(
             activeStop,
             breakEvenStop
        )

        breakEvenDone := true


    if (
         useTrailingStop and
         (
             trailingActive or
             high >= activeEntryPrice + activeRiskDistance * trailStartR
         )
    )

        trailingActive := true

        float candidateTrail = close - atrValue * trailATRMultiplier

        activeStop := math.max(
             activeStop,
             candidateTrail
        )


// ============================================================================
// SECTION 55: SHORT DYNAMIC MANAGEMENT
// ============================================================================

if (
     inShort and
     exitMode == "Source Dynamic" and
     not newShortPosition and
     not na(activeEntryPrice) and
     not na(activeRiskDistance)
)

    if (
         useBreakEven and
         not breakEvenDone and
         low <= activeEntryPrice - activeRiskDistance * breakEvenTriggerR
    )

        float breakEvenStop = activeEntryPrice - breakEvenOffsetPoints

        activeStop := math.min(
             activeStop,
             breakEvenStop
        )

        breakEvenDone := true


    if (
         useTrailingStop and
         (
             trailingActive or
             low <= activeEntryPrice - activeRiskDistance * trailStartR
         )
    )

        trailingActive := true

        float candidateTrail = close + atrValue * trailATRMultiplier

        activeStop := math.min(
             activeStop,
             candidateTrail
        )


// ============================================================================
// SECTION 56: LONG EXIT
// ============================================================================

if (
     inLong and
     not na(activeStop)
)

    string longLossAlert = "EXIT_LONG_SL_CNPS32_V2"


    if breakEvenDone
        longLossAlert := "EXIT_LONG_BE_CNPS32_V2"


    strategy.exit(
         "LX",
         from_entry="Long",
         stop=activeStop,
         limit=activeTakeProfit,
         alert_profit="EXIT_LONG_TP_CNPS32_V2",
         alert_loss=longLossAlert,
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 57: SHORT EXIT
// ============================================================================

if (
     inShort and
     not na(activeStop)
)

    string shortLossAlert = "EXIT_SHORT_SL_CNPS32_V2"


    if breakEvenDone
        shortLossAlert := "EXIT_SHORT_BE_CNPS32_V2"


    strategy.exit(
         "SX",
         from_entry="Short",
         stop=activeStop,
         limit=activeTakeProfit,
         alert_profit="EXIT_SHORT_TP_CNPS32_V2",
         alert_loss=shortLossAlert,
         disable_alert=disableSLTPAlerts
    )


// ============================================================================
// SECTION 58: TIME EXIT
// ============================================================================

if (
     maximumBarsInTrade > 0 and
     strategy.position_size != 0 and
     not na(activeEntryBar)
)

    int barsInTrade = bar_index - activeEntryBar


    if barsInTrade >= maximumBarsInTrade

        strategy.close_all(
             comment="TIME",
             alert_message="TIME_EXIT_CNPS32_V2"
        )


// ============================================================================
// SECTION 59: POSITION CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na

    activeRiskDistance := na

    activeStop := na

    activeTakeProfit := na

    breakEvenDone := false

    trailingActive := false

    activeEntryBar := na


// ============================================================================
// SECTION 60: SESSION END
// ============================================================================

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


if finalSessionBar

    if strategy.position_size != 0

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


// ============================================================================
// SECTION 61: EMA DISPLAY
// ============================================================================

plot(
     showEMA and useEMAFilter ? emaTrend : na,
     title="Trend EMA",
     color=color.orange,
     linewidth=1
)


// ============================================================================
// SECTION 62: ACTIVE STOP
// ============================================================================

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


// ============================================================================
// SECTION 63: ACTIVE TARGET
// ============================================================================

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


// ============================================================================
// SECTION 64: ENTRY SIGNAL MARKERS
// ============================================================================

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


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


// ============================================================================
// SECTION 65: DIAGNOSTIC COUNTERS
// ============================================================================

var int resistanceCreatedCount = 0

var int supportCreatedCount = 0

var int rawBullBreakCount = 0

var int rawBearBreakCount = 0

var int qualifiedLongCount = 0

var int qualifiedShortCount = 0


if not na(pivotHigh) and resistanceValid
    resistanceCreatedCount += 1


if not na(pivotLow) and supportValid
    supportCreatedCount += 1


if rawBreakUp
    rawBullBreakCount += 1


if rawBreakDown
    rawBearBreakCount += 1


if finalLongSignal
    qualifiedLongCount += 1


if finalShortSignal
    qualifiedShortCount += 1


// ============================================================================
// SECTION 66: DIAGNOSTICS
// ============================================================================

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


if showDiagnostics and barstate.islast

    string momentumText = "FLAT"


    if aoValue > aoValue[1]
        momentumText := "RISING"

    else if aoValue < aoValue[1]
        momentumText := "FALLING"


    string positionText = "FLAT"


    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


    string resistanceText = "-"


    if resistanceValid and not resistanceBroken and resistanceFresh
        resistanceText := str.tostring(resistanceLevel, format.mintick)


    string supportText = "-"


    if supportValid and not supportBroken and supportFresh
        supportText := str.tostring(supportLevel, format.mintick)


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


    table.cell(diagnostics, 0, 1, "Resistance")
    table.cell(diagnostics, 1, 1, resistanceText)


    table.cell(diagnostics, 0, 2, "Support")
    table.cell(diagnostics, 1, 2, supportText)


    table.cell(diagnostics, 0, 3, "Res Lines")
    table.cell(diagnostics, 1, 3, str.tostring(resistanceCreatedCount))


    table.cell(diagnostics, 0, 4, "Sup Lines")
    table.cell(diagnostics, 1, 4, str.tostring(supportCreatedCount))


    table.cell(diagnostics, 0, 5, "AO")
    table.cell(diagnostics, 1, 5, str.tostring(aoValue, "0.00"))


    table.cell(diagnostics, 0, 6, "Momentum")
    table.cell(diagnostics, 1, 6, momentumText)


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


    table.cell(diagnostics, 0, 8, "Raw BO Long")
    table.cell(diagnostics, 1, 8, str.tostring(rawBullBreakCount))


    table.cell(diagnostics, 0, 9, "Raw BO Short")
    table.cell(diagnostics, 1, 9, str.tostring(rawBearBreakCount))


    table.cell(diagnostics, 0, 10, "Entries Long")
    table.cell(diagnostics, 1, 10, str.tostring(qualifiedLongCount))


    table.cell(diagnostics, 0, 11, "Entries Short")
    table.cell(diagnostics, 1, 11, str.tostring(qualifiedShortCount))


    table.cell(diagnostics, 0, 12, "Risk")
    table.cell(diagnostics, 1, 12, str.tostring(activeRiskDistance, "0.00"))


    table.cell(diagnostics, 0, 13, "BE")
    table.cell(diagnostics, 1, 13, breakEvenDone ? "ON" : "OFF")


    table.cell(diagnostics, 0, 14, "Trail")
    table.cell(diagnostics, 1, 14, trailingActive ? "ON" : "OFF")


    table.cell(diagnostics, 0, 15, "Exit Mode")
    table.cell(diagnostics, 1, 15, exitMode)


    table.cell(diagnostics, 0, 16, "Reverse")
    table.cell(diagnostics, 1, 16, reverseOnOppositeSignal ? "ON" : "OFF")


    table.cell(diagnostics, 0, 17, "Cooldown")
    table.cell(diagnostics, 1, 17, str.tostring(cooldownBars))


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


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