//@version=6

strategy(
     title="CNPS48 IMPULSE FIB DOUBLE MACD V3 SOURCE FREQUENCY",
     shorttitle="CNPS48 IFDM V3",
     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
)


// ============================================================================
// CNPS48 IMPULSE FIB DOUBLE MACD V3
//
// PURPOSE:
//
// Restore source-like signal frequency after V2 became over-filtered.
//
// DEFAULT TREND ENTRY:
//
// External impulse direction
// + Slow MACD HTF is on correct side of zero
// + External impulse retraces 0.236 -> 0.500
// + Pullback touched during last 5 bars
// + Fast MACD STATE agrees with trend
// = ENTRY
//
// This is intentionally much closer to the original source.
//
// COUNTER-TREND:
// Available but OFF by default.
//
// RISK:
// Structural source-style SL
// + 0.25 ATR buffer
// + 2R target
//
// ============================================================================


// ============================================================================
// 1. ZIGZAG / IMPULSE ENGINE
// ============================================================================

zigzagATRLength = input.int(
     10,
     title="ZigZag ATR Length",
     minval=1,
     group="Impulse Engine"
)

zigzagATRMultiplier = input.float(
     1.00,
     title="ZigZag Reversal x ATR",
     minval=0.10,
     step=0.05,
     group="Impulse Engine"
)

externalEndThreshold = input.float(
     0.50,
     title="External Impulse End Threshold",
     minval=0.10,
     maxval=0.99,
     step=0.001,
     group="Impulse Engine"
)

useWicks = input.bool(
     true,
     title="Use Wicks?",
     group="Impulse Engine"
)


// ============================================================================
// 2. PULLBACK
// ============================================================================

pullbackMin = input.float(
     0.236,
     title="Pullback Zone From",
     minval=0.05,
     maxval=0.95,
     step=0.001,
     group="Trend Pullback"
)

pullbackMax = input.float(
     0.500,
     title="Pullback Zone To",
     minval=0.05,
     maxval=0.99,
     step=0.001,
     group="Trend Pullback"
)

triggerWindow = input.int(
     5,
     title="Trigger Window Bars",
     minval=1,
     maxval=30,
     group="Trend Pullback"
)

useTrendSignals = input.bool(
     true,
     title="Use Trend Pullback?",
     group="Trend Pullback"
)


// ============================================================================
// 3. FAST MACD
// ============================================================================

fastMACDFast = input.int(
     12,
     title="Fast",
     minval=1,
     group="Fast MACD"
)

fastMACDSlow = input.int(
     26,
     title="Slow",
     minval=2,
     group="Fast MACD"
)

fastMACDSignal = input.int(
     9,
     title="Signal",
     minval=1,
     group="Fast MACD"
)

fastTriggerMode = input.string(
     "State",
     title="Trigger Mode",
     options=[
         "State",
         "State + Momentum",
         "Fresh Cross"
     ],
     group="Fast MACD"
)


// ============================================================================
// 4. SLOW MACD
// ============================================================================

slowMACDFast = input.int(
     12,
     title="Fast",
     minval=1,
     group="Slow MACD"
)

slowMACDSlow = input.int(
     26,
     title="Slow",
     minval=2,
     group="Slow MACD"
)

slowMACDSignal = input.int(
     9,
     title="Signal",
     minval=1,
     group="Slow MACD"
)

slowTimeframeMultiplier = input.int(
     4,
     title="Timeframe x Chart",
     minval=1,
     maxval=30,
     group="Slow MACD"
)

slowMACDRule = input.string(
     "Line > 0",
     title="Slow MACD Rule",
     options=[
         "Line > 0",
         "Line > Signal",
         "Both"
     ],
     group="Slow MACD"
)


// ============================================================================
// 5. OPTIONAL QUALITY FILTERS
// ============================================================================

requireZoneReclaim = input.bool(
     false,
     title="Require Shallow Fib Reclaim?",
     group="Optional Quality"
)

useBodyFilter = input.bool(
     false,
     title="Use Trigger Candle Body Filter?",
     group="Optional Quality"
)

minimumBodyRatio = input.float(
     0.20,
     title="Minimum Body / Range",
     minval=0.0,
     maxval=1.0,
     step=0.05,
     group="Optional Quality"
)


// ============================================================================
// 6. COUNTER-TREND
// ============================================================================

useCounterTrendSignals = input.bool(
     false,
     title="Use Counter-Trend Sweep?",
     group="Counter-Trend"
)

sweepCloseBackBars = input.int(
     3,
     title="Close-Back Window",
     minval=1,
     maxval=20,
     group="Counter-Trend"
)

maximumLiquidityLevels = input.int(
     20,
     title="Maximum Liquidity Levels",
     minval=4,
     maxval=60,
     group="Counter-Trend"
)

requireCounterTrendDivergence = input.bool(
     true,
     title="Require MACD Divergence?",
     group="Counter-Trend"
)


// ============================================================================
// 7. RISK
// ============================================================================

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

takeProfitR = input.float(
     2.0,
     title="Target R",
     minval=0.5,
     maxval=10.0,
     step=0.25,
     group="Risk Management"
)

useRiskDistanceFilter = input.bool(
     false,
     title="Use Risk Distance Filter?",
     group="Risk Management"
)

minimumRiskPoints = input.float(
     1.5,
     title="Minimum Risk Points",
     minval=0.5,
     step=0.5,
     group="Risk Management"
)

maximumRiskPoints = input.float(
     12.0,
     title="Maximum Risk Points",
     minval=1.0,
     step=0.5,
     group="Risk Management"
)


// ============================================================================
// 8. BREAK EVEN
// ============================================================================

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

breakEvenTriggerR = input.float(
     1.0,
     title="Break Even Trigger R",
     minval=0.25,
     step=0.25,
     group="Break Even"
)

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


// ============================================================================
// 9. DIRECTION / FREQUENCY
// ============================================================================

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

positionContracts = input.int(
     3,
     title="Contracts",
     minval=1,
     maxval=20,
     group="Trade Control"
)

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

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

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


// ============================================================================
// 10. SESSION
// ============================================================================

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

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

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

lastEntryHour = input.int(
     14,
     title="Last Entry Hour",
     minval=0,
     maxval=23,
     group="Session"
)

lastEntryMinute = input.int(
     30,
     title="Last Entry Minute",
     minval=0,
     maxval=59,
     group="Session"
)

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

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


// ============================================================================
// 11. ALERTS / DISPLAY
// ============================================================================

disableExitAlerts = input.bool(
     false,
     title="Disable Exit Alerts?",
     group="Alerts"
)

showImpulse = input.bool(
     false,
     title="Show External Impulse?",
     group="Display"
)

showPullbackZone = input.bool(
     true,
     title="Show Pullback Zone?",
     group="Display"
)

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

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

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


// ============================================================================
// 12. ATR
// ============================================================================

float atrValue = ta.atr(zigzagATRLength)


// ============================================================================
// 13. PRICE SOURCES
// ============================================================================

float sourceHigh = useWicks ? high : math.max(open, close)
float sourceLow = useWicks ? low : math.min(open, close)


// ============================================================================
// 14. MACD HELPER
// ============================================================================

f_previousMACD(int f, int s, int g) =>
    [m, sg, h] = ta.macd(close, f, s, g)
    [m[1], sg[1]]


// ============================================================================
// 15. FAST MACD
// ============================================================================

[fastMACD, fastSignal, fastHistogram] = ta.macd(
     close,
     fastMACDFast,
     fastMACDSlow,
     fastMACDSignal
)

bool fastBullState = fastMACD > fastSignal
bool fastBearState = fastMACD < fastSignal

bool fastCrossUp = ta.crossover(fastMACD, fastSignal)
bool fastCrossDown = ta.crossunder(fastMACD, fastSignal)

bool fastMomentumUp = (
     fastBullState and
     fastMACD > fastMACD[1]
)

bool fastMomentumDown = (
     fastBearState and
     fastMACD < fastMACD[1]
)

bool fastLongTrigger = false
bool fastShortTrigger = false

if fastTriggerMode == "State"

    fastLongTrigger := fastBullState
    fastShortTrigger := fastBearState

else if fastTriggerMode == "State + Momentum"

    fastLongTrigger := fastMomentumUp
    fastShortTrigger := fastMomentumDown

else

    fastLongTrigger := fastCrossUp
    fastShortTrigger := fastCrossDown


// ============================================================================
// 16. SLOW MACD TIMEFRAME
// ============================================================================

string slowTimeframe = timeframe.period

if slowTimeframeMultiplier > 1

    slowTimeframe := timeframe.from_seconds(
         timeframe.in_seconds(timeframe.period) *
         slowTimeframeMultiplier
    )


// ============================================================================
// 17. SLOW MACD
// ============================================================================

[slowMACDHTF, slowSignalHTF] = request.security(
     syminfo.tickerid,
     slowTimeframe,
     f_previousMACD(
         slowMACDFast,
         slowMACDSlow,
         slowMACDSignal
     ),
     gaps=barmerge.gaps_off,
     lookahead=barmerge.lookahead_on
)

[slowMACDLocal, slowSignalLocal, slowHistLocal] = ta.macd(
     close,
     slowMACDFast,
     slowMACDSlow,
     slowMACDSignal
)

float slowMACD = slowTimeframeMultiplier <= 1 ? slowMACDLocal : slowMACDHTF
float slowSignal = slowTimeframeMultiplier <= 1 ? slowSignalLocal : slowSignalHTF

bool slowBull = false
bool slowBear = false

if slowMACDRule == "Line > 0"

    slowBull := slowMACD > 0
    slowBear := slowMACD < 0

else if slowMACDRule == "Line > Signal"

    slowBull := slowMACD > slowSignal
    slowBear := slowMACD < slowSignal

else

    slowBull := slowMACD > 0 and slowMACD > slowSignal
    slowBear := slowMACD < 0 and slowMACD < slowSignal


// ============================================================================
// 18. ZIGZAG STATE
// ============================================================================

var int zigzagDirection = 0

var float runningExtreme = na
var int runningExtremeBar = na

var float oppositeExtreme = na
var int oppositeExtremeBar = na

var float seedHigh = na
var int seedHighBar = na

var float seedLow = na
var int seedLowBar = na


if zigzagDirection == 0

    if na(seedHigh) or sourceHigh > seedHigh

        seedHigh := sourceHigh
        seedHighBar := bar_index


    if na(seedLow) or sourceLow < seedLow

        seedLow := sourceLow
        seedLowBar := bar_index


else if zigzagDirection == 1

    if sourceHigh > runningExtreme

        runningExtreme := sourceHigh
        runningExtremeBar := bar_index

        oppositeExtreme := na
        oppositeExtremeBar := na


    else if na(oppositeExtreme) or sourceLow < oppositeExtreme

        oppositeExtreme := sourceLow
        oppositeExtremeBar := bar_index


else

    if sourceLow < runningExtreme

        runningExtreme := sourceLow
        runningExtremeBar := bar_index

        oppositeExtreme := na
        oppositeExtremeBar := na


    else if na(oppositeExtreme) or sourceHigh > oppositeExtreme

        oppositeExtreme := sourceHigh
        oppositeExtremeBar := bar_index


// ============================================================================
// 19. CONFIRM ZIGZAG PIVOT
// ============================================================================

float zigzagThreshold = (
     atrValue *
     zigzagATRMultiplier
)

bool newPivot = false
float pivotPrice = na
int pivotBar = na
bool pivotIsHigh = false


if barstate.isconfirmed

    if zigzagDirection == 0

        float downMove = seedHigh - sourceLow
        float upMove = sourceHigh - seedLow

        bool startDown = (
             not na(seedHighBar) and
             seedHighBar < bar_index and
             downMove >= zigzagThreshold
        )

        bool startUp = (
             not na(seedLowBar) and
             seedLowBar < bar_index and
             upMove >= zigzagThreshold
        )


        if startDown and (not startUp or downMove >= upMove)

            newPivot := true
            pivotPrice := seedHigh
            pivotBar := seedHighBar
            pivotIsHigh := true

            zigzagDirection := -1

            runningExtreme := sourceLow
            runningExtremeBar := bar_index

            oppositeExtreme := na
            oppositeExtremeBar := na


        else if startUp

            newPivot := true
            pivotPrice := seedLow
            pivotBar := seedLowBar
            pivotIsHigh := false

            zigzagDirection := 1

            runningExtreme := sourceHigh
            runningExtremeBar := bar_index

            oppositeExtreme := na
            oppositeExtremeBar := na


    else if zigzagDirection == 1

        if (
             not na(oppositeExtreme) and
             runningExtreme - oppositeExtreme >= zigzagThreshold
        )

            newPivot := true
            pivotPrice := runningExtreme
            pivotBar := runningExtremeBar
            pivotIsHigh := true

            zigzagDirection := -1

            runningExtreme := oppositeExtreme
            runningExtremeBar := oppositeExtremeBar

            if runningExtremeBar == bar_index

                oppositeExtreme := na
                oppositeExtremeBar := na

            else

                oppositeExtreme := sourceHigh
                oppositeExtremeBar := bar_index


    else

        if (
             not na(oppositeExtreme) and
             oppositeExtreme - runningExtreme >= zigzagThreshold
        )

            newPivot := true
            pivotPrice := runningExtreme
            pivotBar := runningExtremeBar
            pivotIsHigh := false

            zigzagDirection := 1

            runningExtreme := oppositeExtreme
            runningExtremeBar := oppositeExtremeBar

            if runningExtremeBar == bar_index

                oppositeExtreme := na
                oppositeExtremeBar := na

            else

                oppositeExtreme := sourceLow
                oppositeExtremeBar := bar_index


// ============================================================================
// 20. EXTERNAL IMPULSE STATE
// ============================================================================

var int impulseDirection = 0

var float impulseOrigin = na
var int impulseOriginBar = na

var float impulseExtreme = na
var int impulseExtremeBar = na

var float counterExtreme = na

var bool extremeChanged = false
var bool externalImpulseChanged = false


externalImpulseChanged := false


var float previousPivotPrice = na
var int previousPivotBar = na
var bool havePreviousPivot = false


// ============================================================================
// 21. LIQUIDITY STORAGE
// ============================================================================

var array<float> liquidityPrice = array.new_float()
var array<int> liquidityBar = array.new_int()
var array<bool> liquidityHigh = array.new_bool()
var array<int> liquidityState = array.new_int()
var array<int> liquidityPierce = array.new_int()


// ============================================================================
// 22. PROCESS NEW PIVOT
// ============================================================================

if newPivot

    if havePreviousPivot

        int newDirection = pivotIsHigh ? 1 : -1


        if impulseDirection == 0

            impulseDirection := newDirection

            impulseOrigin := previousPivotPrice
            impulseOriginBar := previousPivotBar

            impulseExtreme := pivotPrice
            impulseExtremeBar := pivotBar

            counterExtreme := na

            extremeChanged := true
            externalImpulseChanged := true


        else if newDirection == impulseDirection

            bool extendsImpulse = false

            if impulseDirection == 1

                extendsImpulse := pivotPrice > impulseExtreme

            else

                extendsImpulse := pivotPrice < impulseExtreme


            if extendsImpulse

                impulseExtreme := pivotPrice
                impulseExtremeBar := pivotBar

                extremeChanged := true


        else

            float span = math.abs(
                 impulseExtreme -
                 impulseOrigin
            )

            float retracement = 1.0

            if span > 0

                retracement := math.abs(
                     pivotPrice -
                     impulseExtreme
                ) / span


            if retracement >= externalEndThreshold

                impulseDirection := newDirection

                impulseOrigin := impulseExtreme
                impulseOriginBar := impulseExtremeBar

                impulseExtreme := pivotPrice
                impulseExtremeBar := pivotBar

                counterExtreme := na

                extremeChanged := true
                externalImpulseChanged := true


    array.push(liquidityPrice, pivotPrice)
    array.push(liquidityBar, pivotBar)
    array.push(liquidityHigh, pivotIsHigh)
    array.push(liquidityState, 0)
    array.push(liquidityPierce, na)


    while array.size(liquidityPrice) > maximumLiquidityLevels

        array.shift(liquidityPrice)
        array.shift(liquidityBar)
        array.shift(liquidityHigh)
        array.shift(liquidityState)
        array.shift(liquidityPierce)


    previousPivotPrice := pivotPrice
    previousPivotBar := pivotBar
    havePreviousPivot := true


// ============================================================================
// 23. LIVE IMPULSE INVALIDATION
// ============================================================================

if (
     impulseDirection != 0 and
     zigzagDirection == -impulseDirection and
     not na(runningExtreme) and
     not na(impulseOrigin)
)

    bool originBroken = false

    if impulseDirection == 1

        originBroken := runningExtreme < impulseOrigin

    else

        originBroken := runningExtreme > impulseOrigin


    if originBroken

        impulseOrigin := impulseExtreme
        impulseOriginBar := impulseExtremeBar

        impulseExtreme := runningExtreme
        impulseExtremeBar := runningExtremeBar

        impulseDirection := -impulseDirection

        counterExtreme := na

        extremeChanged := true
        externalImpulseChanged := true


// ============================================================================
// 24. LIVE EXTENSION
// ============================================================================

if (
     impulseDirection != 0 and
     zigzagDirection == impulseDirection and
     not na(runningExtreme)
)

    bool extendsLive = false

    if impulseDirection == 1

        extendsLive := runningExtreme > impulseExtreme

    else

        extendsLive := runningExtreme < impulseExtreme


    if extendsLive

        impulseExtreme := runningExtreme
        impulseExtremeBar := runningExtremeBar
        extremeChanged := true


// ============================================================================
// 25. COUNTER-EXTREME
// ============================================================================

if impulseDirection == 0

    counterExtreme := na


else if extremeChanged

    extremeChanged := false


    if impulseExtremeBar == bar_index

        counterExtreme := impulseExtreme


    else if impulseDirection == 1

        counterExtreme := math.min(
             sourceLow,
             nz(runningExtreme, sourceLow)
        )


    else

        counterExtreme := math.max(
             sourceHigh,
             nz(runningExtreme, sourceHigh)
        )


else if impulseDirection == 1

    counterExtreme := math.min(
         nz(counterExtreme, sourceLow),
         sourceLow
    )


else

    counterExtreme := math.max(
         nz(counterExtreme, sourceHigh),
         sourceHigh
    )


// ============================================================================
// 26. RETRACEMENT
// ============================================================================

bool impulseValid = (
     impulseDirection != 0 and
     not na(impulseOrigin) and
     not na(impulseExtreme) and
     impulseOrigin != impulseExtreme
)

float impulseSpan = na
float externalRetracement = na


if impulseValid

    impulseSpan := math.abs(
         impulseExtreme -
         impulseOrigin
    )


    if not na(counterExtreme) and impulseSpan > 0

        externalRetracement := math.abs(
             impulseExtreme -
             counterExtreme
        ) / impulseSpan


// ============================================================================
// 27. TREND CONTEXT
// ============================================================================

bool trendUp = (
     impulseDirection == 1 and
     slowBull
)

bool trendDown = (
     impulseDirection == -1 and
     slowBear
)


// ============================================================================
// 28. FIB ZONE
// ============================================================================

bool pullbackActive = (
     impulseValid and
     not na(externalRetracement) and
     externalRetracement >= pullbackMin and
     externalRetracement < pullbackMax
)

float shallowFib = na
float deepFib = na


if impulseValid

    shallowFib := (
         impulseExtreme -
         impulseDirection *
         pullbackMin *
         impulseSpan
    )

    deepFib := (
         impulseExtreme -
         impulseDirection *
         pullbackMax *
         impulseSpan
    )


// ============================================================================
// 29. PULLBACK TOUCH
// ============================================================================

var int pullbackTouchBar = na
var int tradedExtremeBar = na


if externalImpulseChanged

    pullbackTouchBar := na


if pullbackActive

    bool zoneTouched = false


    if impulseDirection == 1

        zoneTouched := sourceLow <= shallowFib

    else

        zoneTouched := sourceHigh >= shallowFib


    if zoneTouched

        pullbackTouchBar := bar_index


bool pullbackRecent = (
     pullbackActive and
     not na(pullbackTouchBar) and
     bar_index -
     pullbackTouchBar <=
     triggerWindow
)


bool alreadyTradedPullback = (
     not na(tradedExtremeBar) and
     tradedExtremeBar ==
     impulseExtremeBar
)


// ============================================================================
// 30. OPTIONAL TRIGGER QUALITY
// ============================================================================

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

float candleBody = math.abs(
     close - open
)

float bodyRatio = (
     candleBody /
     candleRange
)


bool longBodyOK = (
     not useBodyFilter or
     (
         close > open and
         bodyRatio >= minimumBodyRatio
     )
)


bool shortBodyOK = (
     not useBodyFilter or
     (
         close < open and
         bodyRatio >= minimumBodyRatio
     )
)


bool longReclaimOK = (
     not requireZoneReclaim or
     (
         not na(shallowFib) and
         close > shallowFib
     )
)


bool shortReclaimOK = (
     not requireZoneReclaim or
     (
         not na(shallowFib) and
         close < shallowFib
     )
)


// ============================================================================
// 31. SOURCE-LIKE TREND SIGNAL
// ============================================================================

bool buyTrend = (
     barstate.isconfirmed and
     useTrendSignals and
     pullbackRecent and
     not alreadyTradedPullback and
     trendUp and
     fastLongTrigger and
     longReclaimOK and
     longBodyOK
)


bool sellTrend = (
     barstate.isconfirmed and
     useTrendSignals and
     pullbackRecent and
     not alreadyTradedPullback and
     trendDown and
     fastShortTrigger and
     shortReclaimOK and
     shortBodyOK
)


// ============================================================================
// 32. SWEEP DETECTION
// ============================================================================

bool sweepNow = false

var bool lastSweepHigh = false
var float lastSweepLevel = na
var float lastSweepWick = na
var bool lastSweepDivergence = false


int liquidityCount = array.size(
     liquidityPrice
)


if liquidityCount > 0

    for i = 0 to liquidityCount - 1

        float levelPrice = array.get(
             liquidityPrice,
             i
        )

        int levelBar = array.get(
             liquidityBar,
             i
        )

        bool levelHigh = array.get(
             liquidityHigh,
             i
        )

        int state = array.get(
             liquidityState,
             i
        )

        int pierceBar = array.get(
             liquidityPierce,
             i
        )


        if (
             state <= 1 and
             bar_index >
             levelBar
        )

            bool pierced = false


            if levelHigh

                pierced := high > levelPrice

            else

                pierced := low < levelPrice


            if state == 0 and pierced

                state := 1
                pierceBar := bar_index

                array.set(
                     liquidityState,
                     i,
                     state
                )

                array.set(
                     liquidityPierce,
                     i,
                     pierceBar
                )


            if state == 1

                bool closedBack = false


                if levelHigh

                    closedBack := close < levelPrice

                else

                    closedBack := close > levelPrice


                if closedBack

                    state := 2

                    array.set(
                         liquidityState,
                         i,
                         state
                    )

                    sweepNow := true

                    lastSweepHigh := levelHigh
                    lastSweepLevel := levelPrice


                    float wick = levelHigh ? high : low
                    float momentum = fastMACD

                    int barsFromPierce = (
                         bar_index -
                         pierceBar
                    )


                    if barsFromPierce > 0

                        for k = 0 to barsFromPierce

                            if levelHigh

                                wick := math.max(
                                     wick,
                                     high[k]
                                )

                                momentum := math.max(
                                     momentum,
                                     fastMACD[k]
                                )


                            else

                                wick := math.min(
                                     wick,
                                     low[k]
                                )

                                momentum := math.min(
                                     momentum,
                                     fastMACD[k]
                                )


                    bool divergence = false

                    int pivotOffset = (
                         bar_index -
                         levelBar
                    )


                    if pivotOffset < 990

                        if levelHigh

                            divergence := (
                                 momentum <
                                 fastMACD[pivotOffset]
                            )

                        else

                            divergence := (
                                 momentum >
                                 fastMACD[pivotOffset]
                            )


                    lastSweepWick := wick
                    lastSweepDivergence := divergence


                else if (
                     bar_index -
                     pierceBar >=
                     sweepCloseBackBars
                )

                    state := 3

                    array.set(
                         liquidityState,
                         i,
                         state
                    )


// ============================================================================
// 33. COUNTER-TREND ARM
// ============================================================================

var int counterSetupBar = na
var int counterDirection = 0
var float counterStopWick = na
var float counterLevel = na


if (
     useCounterTrendSignals and
     sweepNow and
     (
         not requireCounterTrendDivergence or
         lastSweepDivergence
     )
)

    counterSetupBar := bar_index

    counterDirection := lastSweepHigh ? -1 : 1

    counterStopWick := lastSweepWick

    counterLevel := lastSweepLevel


// ============================================================================
// 34. COUNTER-TREND EXPIRY
// ============================================================================

if counterDirection != 0

    bool counterExpired = (
         bar_index -
         counterSetupBar >
         triggerWindow
    )

    bool counterInvalidated = false


    if counterDirection == -1

        counterInvalidated := high > counterStopWick

    else

        counterInvalidated := low < counterStopWick


    if counterExpired or counterInvalidated

        counterDirection := 0


// ============================================================================
// 35. COUNTER-TREND SIGNAL
// ============================================================================

bool buyCounter = (
     barstate.isconfirmed and
     useCounterTrendSignals and
     counterDirection == 1 and
     fastLongTrigger and
     close > counterLevel
)


bool sellCounter = (
     barstate.isconfirmed and
     useCounterTrendSignals and
     counterDirection == -1 and
     fastShortTrigger and
     close < counterLevel
)


// ============================================================================
// 36. COMBINED SIGNAL
// ============================================================================

bool rawLongSignal = (
     buyTrend or
     buyCounter
)

bool rawShortSignal = (
     sellTrend or
     sellCounter
)

bool sourceLongSignal = rawLongSignal

bool sourceShortSignal = (
     rawShortSignal and
     not rawLongSignal
)

bool signalIsTrend = (
     buyTrend or
     sellTrend
)


// ============================================================================
// 37. SIGNAL STOP
// ============================================================================

float signalStopPrice = na
float signalRiskDistance = na


if sourceLongSignal or sourceShortSignal

    float stopBuffer = (
         atrValue *
         stopBufferATR
    )


    if signalIsTrend

        if sourceLongSignal

            signalStopPrice := (
                 math.min(
                     nz(counterExtreme, low),
                     low
                 ) -
                 stopBuffer
            )

        else

            signalStopPrice := (
                 math.max(
                     nz(counterExtreme, high),
                     high
                 ) +
                 stopBuffer
            )


    else

        if sourceLongSignal

            signalStopPrice := (
                 counterStopWick -
                 stopBuffer
            )

        else

            signalStopPrice := (
                 counterStopWick +
                 stopBuffer
            )


    if sourceLongSignal

        signalRiskDistance := (
             close -
             signalStopPrice
        )

    else

        signalRiskDistance := (
             signalStopPrice -
             close
        )


// ============================================================================
// 38. RISK VALIDATION
// ============================================================================

bool positiveRisk = (
     not na(signalRiskDistance) and
     signalRiskDistance >
     syminfo.mintick
)


bool riskDistanceOK = true


if useRiskDistanceFilter

    riskDistanceOK := (
         signalRiskDistance >=
         minimumRiskPoints and
         signalRiskDistance <=
         maximumRiskPoints
    )


// ============================================================================
// 39. SESSION
// ============================================================================

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


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


int entryStartTotal = (
     entryStartHour *
     60 +
     entryStartMinute
)


int lastEntryTotal = (
     lastEntryHour *
     60 +
     lastEntryMinute
)


int forceCloseTotal = (
     forceCloseHour *
     60 +
     forceCloseMinute
)


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


// ============================================================================
// 40. DAILY STATE
// ============================================================================

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


// ============================================================================
// 41. 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
)


// ============================================================================
// 42. COOLDOWN / TRADE LIMIT
// ============================================================================

var int lastExitBar = na


if positionJustClosed

    lastExitBar := bar_index


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


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


// ============================================================================
// 43. FINAL ENTRY
// ============================================================================

bool commonEntryOK = (
     flat and
     entrySession and
     cooldownOK and
     tradeLimitOK and
     positiveRisk and
     riskDistanceOK
)


bool finalLongSignal = (
     commonEntryOK and
     allowLong and
     sourceLongSignal
)


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


// ============================================================================
// 44. PENDING TRADE STATE
// ============================================================================

var float pendingStopPrice = na
var string pendingSetup = ""



// ============================================================================
// 45. LONG ENTRY
// ============================================================================

if finalLongSignal

    pendingStopPrice := signalStopPrice

    pendingSetup := (
         signalIsTrend
         ? "TREND"
         : "COUNTER"
    )


    strategy.entry(
         "Long",
         strategy.long,
         qty=positionContracts,
         comment=signalIsTrend ? "Trend Long" : "Sweep Long",
         alert_message=signalIsTrend ? "ENTRY_LONG_TREND_CNPS48_V3" : "ENTRY_LONG_CT_CNPS48_V3"
    )


    tradesToday += 1


    if signalIsTrend

        tradedExtremeBar := impulseExtremeBar

    else

        counterDirection := 0


// ============================================================================
// 46. SHORT ENTRY
// ============================================================================

if finalShortSignal

    pendingStopPrice := signalStopPrice

    pendingSetup := (
         signalIsTrend
         ? "TREND"
         : "COUNTER"
    )


    strategy.entry(
         "Short",
         strategy.short,
         qty=positionContracts,
         comment=signalIsTrend ? "Trend Short" : "Sweep Short",
         alert_message=signalIsTrend ? "ENTRY_SHORT_TREND_CNPS48_V3" : "ENTRY_SHORT_CT_CNPS48_V3"
    )


    tradesToday += 1


    if signalIsTrend

        tradedExtremeBar := impulseExtremeBar

    else

        counterDirection := 0


// ============================================================================
// 47. ACTIVE TRADE STATE
// ============================================================================

var float activeEntryPrice = na
var float activeInitialRisk = na
var float activeStopPrice = na
var float activeTargetPrice = na

var float maximumProfitR = 0.0

var bool breakEvenActivated = false

var string activeSetup = ""



// ============================================================================
// 48. INITIALIZE LONG
// ============================================================================

if newLongPosition

    activeEntryPrice := (
         strategy.position_avg_price
    )

    activeStopPrice := (
         pendingStopPrice
    )

    activeInitialRisk := (
         activeEntryPrice -
         activeStopPrice
    )

    activeTargetPrice := (
         activeEntryPrice +
         activeInitialRisk *
         takeProfitR
    )

    activeSetup := pendingSetup

    maximumProfitR := 0.0
    breakEvenActivated := false

    pendingStopPrice := na
    pendingSetup := ""



// ============================================================================
// 49. INITIALIZE SHORT
// ============================================================================

if newShortPosition

    activeEntryPrice := (
         strategy.position_avg_price
    )

    activeStopPrice := (
         pendingStopPrice
    )

    activeInitialRisk := (
         activeStopPrice -
         activeEntryPrice
    )

    activeTargetPrice := (
         activeEntryPrice -
         activeInitialRisk *
         takeProfitR
    )

    activeSetup := pendingSetup

    maximumProfitR := 0.0
    breakEvenActivated := false

    pendingStopPrice := na
    pendingSetup := ""



// ============================================================================
// 50. PROFIT R
// ============================================================================

float longProfitR = na
float shortProfitR = na


if (
     inLong and
     not na(activeInitialRisk) and
     activeInitialRisk >
     0
)

    longProfitR := (
         high -
         activeEntryPrice
    ) / activeInitialRisk


if (
     inShort and
     not na(activeInitialRisk) and
     activeInitialRisk >
     0
)

    shortProfitR := (
         activeEntryPrice -
         low
    ) / activeInitialRisk


if (
     inLong and
     not na(longProfitR)
)

    maximumProfitR := math.max(
         maximumProfitR,
         longProfitR
    )


if (
     inShort and
     not na(shortProfitR)
)

    maximumProfitR := math.max(
         maximumProfitR,
         shortProfitR
    )


// ============================================================================
// 51. BREAK EVEN
// ============================================================================

if (
     inLong and
     useBreakEven and
     not breakEvenActivated and
     maximumProfitR >=
     breakEvenTriggerR
)

    activeStopPrice := math.max(
         activeStopPrice,
         activeEntryPrice +
         breakEvenOffset
    )

    breakEvenActivated := true


if (
     inShort and
     useBreakEven and
     not breakEvenActivated and
     maximumProfitR >=
     breakEvenTriggerR
)

    activeStopPrice := math.min(
         activeStopPrice,
         activeEntryPrice -
         breakEvenOffset
    )

    breakEvenActivated := true


// ============================================================================
// 52. EXITS
// ============================================================================

if (
     inLong and
     not na(activeStopPrice) and
     not na(activeTargetPrice)
)

    strategy.exit(
         "Long Exit",
         from_entry="Long",
         stop=activeStopPrice,
         limit=activeTargetPrice,
         alert_profit="EXIT_LONG_TP_CNPS48_V3",
         alert_loss=breakEvenActivated ? "EXIT_LONG_BE_CNPS48_V3" : "EXIT_LONG_SL_CNPS48_V3",
         disable_alert=disableExitAlerts
    )


if (
     inShort and
     not na(activeStopPrice) and
     not na(activeTargetPrice)
)

    strategy.exit(
         "Short Exit",
         from_entry="Short",
         stop=activeStopPrice,
         limit=activeTargetPrice,
         alert_profit="EXIT_SHORT_TP_CNPS48_V3",
         alert_loss=breakEvenActivated ? "EXIT_SHORT_BE_CNPS48_V3" : "EXIT_SHORT_SL_CNPS48_V3",
         disable_alert=disableExitAlerts
    )


// ============================================================================
// 53. FORCE CLOSE
// ============================================================================

bool forceCloseBar = (
     currentMinute <
     forceCloseTotal and
     currentCloseMinute >=
     forceCloseTotal
)


if (
     forceCloseBar and
     strategy.position_size != 0
)

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


// ============================================================================
// 54. CLEANUP
// ============================================================================

if positionJustClosed

    activeEntryPrice := na
    activeInitialRisk := na
    activeStopPrice := na
    activeTargetPrice := na

    maximumProfitR := 0.0
    breakEvenActivated := false

    activeSetup := ""



// ============================================================================
// 55. DISPLAY
// ============================================================================

plot(
     showImpulse ? impulseOrigin : na,
     title="External Origin",
     color=color.gray,
     style=plot.style_linebr
)


plot(
     showImpulse ? impulseExtreme : na,
     title="External Extreme",
     color=impulseDirection == 1 ? color.green : color.red,
     linewidth=2,
     style=plot.style_linebr
)


plot(
     showPullbackZone ? shallowFib : na,
     title="Fib Shallow",
     color=color.orange,
     style=plot.style_linebr
)


plot(
     showPullbackZone ? deepFib : na,
     title="Fib Deep",
     color=color.new(color.orange, 50),
     style=plot.style_linebr
)


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


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


// ============================================================================
// 56. SIGNAL MARKERS
// ============================================================================

plotshape(
     showSignalMarkers and
     buyTrend,
     title="Trend Buy",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.green,
     size=size.small,
     text="T"
)


plotshape(
     showSignalMarkers and
     sellTrend,
     title="Trend Sell",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.red,
     size=size.small,
     text="T"
)


plotshape(
     showSignalMarkers and
     buyCounter,
     title="Counter Buy",
     style=shape.triangleup,
     location=location.belowbar,
     color=color.new(color.green, 50),
     size=size.tiny,
     text="CT"
)


plotshape(
     showSignalMarkers and
     sellCounter,
     title="Counter Sell",
     style=shape.triangledown,
     location=location.abovebar,
     color=color.new(color.red, 50),
     size=size.tiny,
     text="CT"
)


// ============================================================================
// 57. DATA WINDOW
// ============================================================================

plot(
     externalRetracement,
     title="External Retracement",
     display=display.data_window
)

plot(
     fastMACD,
     title="Fast MACD",
     display=display.data_window
)

plot(
     fastSignal,
     title="Fast Signal",
     display=display.data_window
)

plot(
     slowMACD,
     title="Slow MACD",
     display=display.data_window
)

plot(
     slowSignal,
     title="Slow Signal",
     display=display.data_window
)

plot(
     signalRiskDistance,
     title="Signal Risk",
     display=display.data_window
)

plot(
     activeInitialRisk,
     title="Active Risk",
     display=display.data_window
)


// ============================================================================
// 58. DIAGNOSTICS
// ============================================================================

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


if (
     showDiagnostics and
     barstate.islast
)

    string impulseText = "NONE"

    if impulseDirection == 1
        impulseText := "BULL"

    else if impulseDirection == -1
        impulseText := "BEAR"


    string positionText = "FLAT"

    if inLong
        positionText := "LONG"

    else if inShort
        positionText := "SHORT"


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

    table.cell(diagnostics, 0, 1, "Impulse")
    table.cell(diagnostics, 1, 1, impulseText)

    table.cell(diagnostics, 0, 2, "Retracement")
    table.cell(
         diagnostics,
         1,
         2,
         str.tostring(
             externalRetracement,
             "0.000"
         )
    )

    table.cell(diagnostics, 0, 3, "PB Active")
    table.cell(
         diagnostics,
         1,
         3,
         pullbackActive ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 4, "PB Recent")
    table.cell(
         diagnostics,
         1,
         4,
         pullbackRecent ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 5, "Slow MACD")
    table.cell(
         diagnostics,
         1,
         5,
         slowBull ? "BULL" : slowBear ? "BEAR" : "NEUTRAL"
    )

    table.cell(diagnostics, 0, 6, "Fast State")
    table.cell(
         diagnostics,
         1,
         6,
         fastBullState ? "BULL" : fastBearState ? "BEAR" : "NEUTRAL"
    )

    table.cell(diagnostics, 0, 7, "Trend Buy")
    table.cell(
         diagnostics,
         1,
         7,
         buyTrend ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 8, "Trend Sell")
    table.cell(
         diagnostics,
         1,
         8,
         sellTrend ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 9, "Risk")
    table.cell(
         diagnostics,
         1,
         9,
         str.tostring(
             signalRiskDistance,
             "0.00"
         )
    )

    table.cell(diagnostics, 0, 10, "Risk OK")
    table.cell(
         diagnostics,
         1,
         10,
         positiveRisk and riskDistanceOK ? "YES" : "NO"
    )

    table.cell(diagnostics, 0, 11, "Session")
    table.cell(
         diagnostics,
         1,
         11,
         entrySession ? "YES" : "NO"
    )

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

    table.cell(diagnostics, 0, 13, "Setup")
    table.cell(
         diagnostics,
         1,
         13,
         activeSetup == "" ? "-" : activeSetup
    )

    table.cell(diagnostics, 0, 14, "Stop")
    table.cell(
         diagnostics,
         1,
         14,
         str.tostring(
             activeStopPrice,
             format.mintick
         )
    )

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