Strategy · Pine Script v6 · On the chart

Ranger V2 POC State Delta Session Profiles

by rosijay17-cmd

A strategy that trades liquidity sweeps within a trend context.

View on GitHub

Repaints

GitHub stars1on the repository
Pine versionv6current
Lines of code2,058
Settings40inputs
LicenceGPL-3.0
Last update7 days ago22 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new strategy, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart. The trades and results appear in the Strategy Tester tab; adjust the settings with the gear icon.

Source Code

2,058 lines of Pine Script v6 from Ranger_V2_POC_State_Delta_Session_Profiles.pine, published under GPL-3.0. Keep the author's credit and licence lines when you reuse it.

Ranger_V2_POC_State_Delta_Session_Profiles.pine
//@version=6
strategy(
     title="Ranger V2 — POC State + Delta + Session Profiles",
     shorttitle="Ranger V2",
     overlay=true,
     pyramiding=0,
     process_orders_on_close=true,
     calc_on_every_tick=true,
     calc_on_order_fills=true,
     initial_capital=50000,
     default_qty_type=strategy.fixed,
     default_qty_value=1,
     commission_type=strategy.commission.percent,
     commission_value=0.02,
     slippage=1,
     max_labels_count=500,
     max_lines_count=500,
     max_boxes_count=500)

// ═══════════════════════════════════════════════════════════════════
// GROUPS
// ═══════════════════════════════════════════════════════════════════

const string GRP_TREND   = "01 — Trend"
const string GRP_MOM     = "02 — Legacy Momentum"
const string GRP_SWEEP   = "03 — Liquidity Sweep"
const string GRP_STATE   = "04 — POC State Engine"
const string GRP_DELTA   = "05 — LTF Volume Delta"
const string GRP_FREQ    = "06 — Trade Frequency"
const string GRP_SESSION = "07 — Trading Session"
const string GRP_SIZE    = "08 — Position Sizing"
const string GRP_RISK    = "09 — Risk Management"
const string GRP_TRAIL   = "10 — Trailing Stop"
const string GRP_PROFILE = "11 — Session Volume Profiles"
const string GRP_VISUAL  = "12 — Visuals"
const string GRP_ALERTS  = "13 — Alerts"

// ═══════════════════════════════════════════════════════════════════
// TREND
// ═══════════════════════════════════════════════════════════════════

int trendFastLen = input.int(20, "Fast EMA", minval=2, group=GRP_TREND)
int trendSlowLen = input.int(50, "Slow EMA", minval=3, group=GRP_TREND)
int adxLen = input.int(14, "ADX Length", minval=2, group=GRP_TREND)
float adxMin = input.float(15.0, "Minimum ADX", minval=1.0, step=0.5, group=GRP_TREND)
bool requireDIAlignment = input.bool(true, "Require DI Alignment", group=GRP_TREND)
bool useHtfTrendFilter = input.bool(true, "Use Confirmed HTF Trend", group=GRP_TREND)
string htfTimeframe = input.timeframe("60", "HTF", group=GRP_TREND)
int htfFastLen = input.int(20, "HTF Fast EMA", minval=2, group=GRP_TREND)
int htfSlowLen = input.int(50, "HTF Slow EMA", minval=3, group=GRP_TREND)

// ═══════════════════════════════════════════════════════════════════
// LEGACY MOMENTUM
// Delta is primary. ROC/BOP is optional.
// ═══════════════════════════════════════════════════════════════════

bool requireMomentumAgreement = input.bool(false, "Require ROC / BOP Agreement", group=GRP_MOM)
int rocLen = input.int(14, "ROC Length", minval=2, group=GRP_MOM)
float rocMinAbs = input.float(0.05, "Minimum |ROC|", minval=0.0, step=0.05, group=GRP_MOM)
int bopLen = input.int(14, "BOP Smoothing", minval=2, group=GRP_MOM)

// ═══════════════════════════════════════════════════════════════════
// SWEEP
// ═══════════════════════════════════════════════════════════════════

int atrLen = input.int(14, "ATR Length", minval=2, group=GRP_SWEEP)
int sweepPivotLength = input.int(5, "Swing Pivot Length", minval=1, group=GRP_SWEEP)
int sweepStoredLevels = input.int(20, "Stored Liquidity Levels", minval=5, maxval=100, group=GRP_SWEEP)
float sweepMinDistanceAtr = input.float(0.10, "Minimum Sweep Distance / ATR", minval=0.01, step=0.01, group=GRP_SWEEP)
string sweepReclaimRule = input.string("Close Back Inside", "Sweep Reclaim Rule", options=["Close Back Inside", "Strong Reclaim"], group=GRP_SWEEP)
float sweepMinWickPercent = input.float(0.25, "Minimum Wick %", minval=0.0, maxval=1.0, step=0.01, group=GRP_SWEEP)
float sweepMaxBodyPercent = input.float(0.75, "Maximum Body %", minval=0.05, maxval=1.0, step=0.01, group=GRP_SWEEP)
float sweepMinRangeAtr = input.float(0.15, "Minimum Candle Range / ATR", minval=0.01, step=0.01, group=GRP_SWEEP)
bool sweepRequireBullishBody = input.bool(true, "Long Reclaim Must Be Bullish", group=GRP_SWEEP)
bool sweepRequireBearishBody = input.bool(true, "Short Reclaim Must Be Bearish", group=GRP_SWEEP)
bool sweepRequireMidlineBreak = input.bool(true, "Require Sweep Midline Reclaim", group=GRP_SWEEP)

// ═══════════════════════════════════════════════════════════════════
// STATE ENGINE
// ═══════════════════════════════════════════════════════════════════

int setupWindowBars = input.int(6, "Setup Memory — Bars", minval=2, maxval=30, tooltip="Evidence may arrive in different orders, but all evidence must belong to the same setup window.", group=GRP_STATE)
float pocAnchorDistanceAtr = input.float(0.55, "Sweep ↔ POC Distance — ATR", minval=0.05, maxval=3.0, step=0.05, group=GRP_STATE)
float setupInvalidationAtr = input.float(1.25, "Setup Invalidation Distance — ATR", minval=0.25, maxval=5.0, step=0.05, group=GRP_STATE)
bool invalidateOnOppositeSweep = input.bool(true, "Opposite Sweep Invalidates Setup", group=GRP_STATE)
bool useRelativeVolumeConfirm = input.bool(true, "Require Relative Volume", group=GRP_STATE)
int relativeVolLen = input.int(20, "Relative Volume Average", minval=2, group=GRP_STATE)
float relativeVolMin = input.float(1.10, "Minimum Relative Volume", minval=0.10, step=0.05, group=GRP_STATE)
bool usePocMigrationConfirm = input.bool(false, "Require POC Migration", tooltip="Long requires rising developing POC. Short requires falling developing POC.", group=GRP_STATE)
int pocMigrationLookback = input.int(3, "POC Migration Lookback", minval=1, maxval=20, group=GRP_STATE)

// ═══════════════════════════════════════════════════════════════════
// LTF DELTA
// ═══════════════════════════════════════════════════════════════════

string deltaLtf = input.timeframe("2", "Delta Lower Timeframe", tooltip="Recommended starting value on a 10-minute chart: 2 minutes.", group=GRP_DELTA)
bool deltaFallbackToBar = input.bool(true, "Fallback To Bar Estimate", tooltip="Uses candle-location volume estimation when intrabar data is unavailable.", group=GRP_DELTA)
int minDeltaIntrabars = input.int(3, "Minimum Intrabars", minval=1, maxval=100, group=GRP_DELTA)
float deltaPctThreshold = input.float(8.0, "Directional Delta % Threshold", minval=0.0, maxval=100.0, step=0.5, group=GRP_DELTA)
float directionalSubbarShare = input.float(0.55, "Directional Sub-Bar Share", minval=0.50, maxval=1.0, step=0.05, group=GRP_DELTA)
bool requireStackedSubbars = input.bool(true, "Require Stacked Directional Sub-Bars", group=GRP_DELTA)
int minStackedSubbars = input.int(2, "Minimum Stacked Sub-Bars", minval=1, maxval=20, group=GRP_DELTA)
string deltaConfirmationMode = input.string("Directional OR Absorption", "Delta Confirmation Mode", options=["Directional", "Directional OR Absorption", "Directional + Absorption"], tooltip="The + mode does not impose an event sequence. Both behaviors only need to occur inside the same active setup.", group=GRP_DELTA)
int absorptionLookback = input.int(6, "Absorption / Divergence Lookback", minval=2, maxval=50, group=GRP_DELTA)
float absorptionDeltaPct = input.float(8.0, "Absorption Opposing Delta %", minval=0.0, step=0.5, group=GRP_DELTA)
float absorptionCloseLocation = input.float(0.60, "Absorption Close Location", minval=0.50, maxval=0.95, step=0.05, group=GRP_DELTA)
float divergenceDeltaImprovement = input.float(5.0, "Delta Divergence Improvement %", minval=0.0, step=0.5, group=GRP_DELTA)

// ═══════════════════════════════════════════════════════════════════
// FREQUENCY
// ═══════════════════════════════════════════════════════════════════

string tradeDirection = input.string("Both", "Trade Direction", options=["Long Only", "Short Only", "Both"], group=GRP_FREQ)
int cooldownBars = input.int(8, "Cooldown Bars After Trade", minval=0, maxval=200, group=GRP_FREQ)
int minBarsBetweenEntries = input.int(5, "Minimum Bars Between Entries", minval=0, maxval=100, group=GRP_FREQ)
int maxTradesPerDay = input.int(5, "Maximum Trades Per Day", minval=1, maxval=20, group=GRP_FREQ)

// ═══════════════════════════════════════════════════════════════════
// TRADING SESSION
// ═══════════════════════════════════════════════════════════════════

bool useSessionFilter = input.bool(false, "Restrict Entries To Session", group=GRP_SESSION)
string tradeSession = input.session("0930-1600", "Entry Session", group=GRP_SESSION)
string sessionTimezone = input.string("America/New_York", "Session Timezone", group=GRP_SESSION)
bool closeAtSessionEnd = input.bool(false, "Close Position At Session End", group=GRP_SESSION)

// ═══════════════════════════════════════════════════════════════════
// POSITION SIZE
// ═══════════════════════════════════════════════════════════════════

string sizingMode = input.string("Fixed", "Sizing Mode", options=["Fixed", "Risk %", "Risk $"], group=GRP_SIZE)
float baseQty = input.float(1.0, "Fixed Contracts", minval=1.0, step=1.0, group=GRP_SIZE)
float riskPerTradePct = input.float(0.50, "Risk Per Trade %", minval=0.05, maxval=10.0, step=0.05, group=GRP_SIZE)
float maxRiskDollars = input.float(500.0, "Risk $", minval=1.0, step=25.0, group=GRP_SIZE)

// ═══════════════════════════════════════════════════════════════════
// RISK
// ═══════════════════════════════════════════════════════════════════

float stopBufferATR = input.float(0.15, "Stop Buffer Beyond Swept Level — ATR", minval=0.0, step=0.05, group=GRP_RISK)
float maxStopATR = input.float(2.0, "Maximum Stop Distance — ATR", minval=0.25, step=0.25, group=GRP_RISK)
float tp1R = input.float(1.0, "TP1 — R", minval=0.25, step=0.25, group=GRP_RISK)
float tp2R = input.float(2.5, "TP2 — R", minval=0.50, step=0.25, group=GRP_RISK)
float tp1Qty = input.float(50.0, "TP1 Quantity %", minval=1.0, maxval=99.0, step=1.0, group=GRP_RISK)
bool useBreakEven = input.bool(true, "Break Even After TP1", group=GRP_RISK)
int breakEvenOffsetTicks = input.int(0, "Break-Even Offset — Ticks", minval=0, maxval=100, group=GRP_RISK)
int maximumHoldingBars = input.int(100, "Maximum Holding Bars", minval=1, maxval=2000, group=GRP_RISK)

// ═══════════════════════════════════════════════════════════════════
// TRAILING
// ═══════════════════════════════════════════════════════════════════

bool useTrail = input.bool(true, "Trail After TP1", group=GRP_TRAIL)
float tightTrailATR = input.float(1.0, "Tight Trail ATR", minval=0.1, step=0.1, group=GRP_TRAIL)
float looseTrailATR = input.float(2.0, "Loose Trail ATR", minval=0.1, step=0.1, group=GRP_TRAIL)

// ═══════════════════════════════════════════════════════════════════
// SESSION PROFILE
// ═══════════════════════════════════════════════════════════════════

string profileSessionType = input.string("Custom", "Profile Session Type", options=["Custom", "All"], group=GRP_PROFILE)
string profileSession = input.session("0930-1600", "Profile Session", group=GRP_PROFILE)
int profileRows = input.int(24, "Profile Rows", minval=10, maxval=40, tooltip="Each row can use two boxes. 24-30 is a good balance between visual resolution and Pine object usage.", group=GRP_PROFILE)
int valueAreaPct = input.int(70, "Value Area %", minval=1, maxval=100, group=GRP_PROFILE)
int profileWidthBars = input.int(12, "Histogram Maximum Width — Bars", minval=3, maxval=30, group=GRP_PROFILE)
string profileAlignment = input.string("Left", "Histogram Alignment", options=["Left", "Right"], group=GRP_PROFILE)
bool showCurrentProfile = input.bool(true, "Show Current Developing Profile", group=GRP_PROFILE)
bool showHistoricalProfiles = input.bool(true, "Show Completed Profiles", group=GRP_PROFILE)
int historicalProfileCount = input.int(4, "Completed Profiles", minval=1, maxval=4, group=GRP_PROFILE)
bool showProfileLevels = input.bool(true, "Show POC / VAH / VAL", group=GRP_PROFILE)
bool showProfileTags = input.bool(true, "Show Current Profile Tags", group=GRP_PROFILE)

// ═══════════════════════════════════════════════════════════════════
// VISUALS
// ═══════════════════════════════════════════════════════════════════

bool showTrendRibbon = input.bool(true, "Show Trend Ribbon", group=GRP_VISUAL)
bool showSignals = input.bool(true, "Show Entry Signals", group=GRP_VISUAL)
bool showRawSweeps = input.bool(false, "Show Raw Sweeps", group=GRP_VISUAL)
bool showAbsorptionMarkers = input.bool(true, "Show Absorption / Divergence", group=GRP_VISUAL)
bool showStateBubbles = input.bool(true, "Show Current Setup State", group=GRP_VISUAL)
bool showTradeOverlay = input.bool(true, "Show Active Trade Overlay", group=GRP_VISUAL)
bool colorTrendBars = input.bool(false, "Color Candles By Trend", group=GRP_VISUAL)
int rightProjectionBars = input.int(10, "Right Projection Bars", minval=3, maxval=50, group=GRP_VISUAL)
bool showDashboard = input.bool(true, "Show Dashboard", group=GRP_VISUAL)
string dashboardPosition = input.string("Top Right", "Dashboard Position", options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group=GRP_VISUAL)
string dashboardFont = input.string("Phone", "Dashboard Font", options=["Compact", "Phone", "Large"], group=GRP_VISUAL)

// ═══════════════════════════════════════════════════════════════════
// ALERTS
// ═══════════════════════════════════════════════════════════════════

bool enableDynamicAlerts = input.bool(true, "Enable Dynamic Alerts", group=GRP_ALERTS)

// ═══════════════════════════════════════════════════════════════════
// COLORS
// ═══════════════════════════════════════════════════════════════════

color C_NAVY   = color.rgb(10, 15, 25)
color C_CARD   = color.rgb(21, 29, 43)
color C_ALT    = color.rgb(27, 36, 51)
color C_BLUE   = color.rgb(34, 159, 217)
color C_SELL   = color.rgb(220, 82, 126)
color C_GREEN  = color.rgb(16, 185, 129)
color C_ORANGE = color.rgb(245, 158, 11)
color C_RED    = color.rgb(239, 68, 68)
color C_GRAY   = color.rgb(107, 114, 128)
color C_WHITE  = color.rgb(245, 247, 250)

// ═══════════════════════════════════════════════════════════════════
// HELPERS
// ═══════════════════════════════════════════════════════════════════

f_price(float value) =>
    na(value) ? "—" : str.tostring(value, format.mintick)

f_number(float value) =>
    na(value) ? "—" : str.tostring(value, "#.00")

f_recent(int eventBar, int windowBars) =>
    not na(eventBar) and bar_index - eventBar <= windowBars

f_alertMsg(string kind, string label, float px) =>
    kind + " | " + label + " | " + syminfo.ticker + " " + timeframe.period + " @ " + str.tostring(px, format.mintick)

// ═══════════════════════════════════════════════════════════════════
// BASE INDICATORS
// ═══════════════════════════════════════════════════════════════════

float atr = ta.atr(atrLen)
float emaFast = ta.ema(close, trendFastLen)
float emaSlow = ta.ema(close, trendSlowLen)
[plusDI, minusDI, adx] = ta.dmi(adxLen, adxLen)
float roc = ta.roc(close, rocLen)
float candleRange = math.max(high - low, syminfo.mintick)
float candleCloseLocation = math.max(0.0, math.min(1.0, (close - low) / candleRange))
float bopRaw = (close - open) / candleRange
float bop = ta.ema(bopRaw, bopLen)

bool dataReady =
     not na(atr) and
     not na(emaFast) and
     not na(emaSlow) and
     not na(adx) and
     not na(roc) and
     not na(bop)

// ═══════════════════════════════════════════════════════════════════
// HTF TREND
// ═══════════════════════════════════════════════════════════════════

float htfEmaFast = request.security(syminfo.tickerid, htfTimeframe, ta.ema(close, htfFastLen), lookahead=barmerge.lookahead_off)
float htfEmaSlow = request.security(syminfo.tickerid, htfTimeframe, ta.ema(close, htfSlowLen), lookahead=barmerge.lookahead_off)

bool htfValid = not na(htfEmaFast) and not na(htfEmaSlow)
bool htfBullish = htfValid and htfEmaFast > htfEmaSlow
bool htfBearish = htfValid and htfEmaFast < htfEmaSlow
bool longHtfOk = not useHtfTrendFilter or htfBullish
bool shortHtfOk = not useHtfTrendFilter or htfBearish

// ═══════════════════════════════════════════════════════════════════
// LIVE TREND / MOMENTUM GATES
// ═══════════════════════════════════════════════════════════════════

bool localBullish = emaFast > emaSlow and close > emaFast
bool localBearish = emaFast < emaSlow and close < emaFast
bool adxOk = adx >= adxMin
bool diLongOk = not requireDIAlignment or plusDI > minusDI
bool diShortOk = not requireDIAlignment or minusDI > plusDI

bool longTrendOk =
     dataReady and
     localBullish and
     adxOk and
     diLongOk and
     longHtfOk

bool shortTrendOk =
     dataReady and
     localBearish and
     adxOk and
     diShortOk and
     shortHtfOk

bool longMomentumOk =
     not requireMomentumAgreement or
     (roc >= rocMinAbs and bop > 0.0)

bool shortMomentumOk =
     not requireMomentumAgreement or
     (roc <= -rocMinAbs and bop < 0.0)

bool longRegimeOk = longTrendOk and longMomentumOk
bool shortRegimeOk = shortTrendOk and shortMomentumOk

// ═══════════════════════════════════════════════════════════════════
// SESSION / DAILY LIMIT
// ═══════════════════════════════════════════════════════════════════

bool inSessionRaw = not na(time(timeframe.period, tradeSession, sessionTimezone))
bool inTradeSession = not useSessionFilter or inSessionRaw
bool sessionEndBar = useSessionFilter and not inSessionRaw and inSessionRaw[1]

int dayIdentifier =
     year(time, sessionTimezone) * 10000 +
     month(time, sessionTimezone) * 100 +
     dayofmonth(time, sessionTimezone)

bool newTradingDay = barstate.isfirst or dayIdentifier != dayIdentifier[1]

var int tradesToday = 0
if newTradingDay
    tradesToday := 0

bool dailyTradeLimitPass = tradesToday < maxTradesPerDay

// ═══════════════════════════════════════════════════════════════════
// COOLDOWN / ENTRY SPACING
// ═══════════════════════════════════════════════════════════════════

int latestClosedTradeBar =
     strategy.closedtrades > 0
     ? strategy.closedtrades.exit_bar_index(strategy.closedtrades - 1)
     : na

int barsSinceClosedTrade =
     na(latestClosedTradeBar)
     ? 1000000
     : bar_index - latestClosedTradeBar

bool cooldownPass = barsSinceClosedTrade >= cooldownBars

int cooldownRemaining =
     cooldownPass
     ? 0
     : math.max(cooldownBars - barsSinceClosedTrade, 0)

var int lastEntrySignalBar = na

bool entrySpacingPass =
     na(lastEntrySignalBar) or
     bar_index - lastEntrySignalBar > minBarsBetweenEntries

bool tradeDirectionLongOk = tradeDirection == "Long Only" or tradeDirection == "Both"
bool tradeDirectionShortOk = tradeDirection == "Short Only" or tradeDirection == "Both"

bool commonLiveGate =
     inTradeSession and
     dailyTradeLimitPass and
     cooldownPass and
     entrySpacingPass

// ═══════════════════════════════════════════════════════════════════
// LTF DELTA
// ═══════════════════════════════════════════════════════════════════

[ltfOpenArray, ltfCloseArray, ltfVolumeArray] =
     request.security_lower_tf(
          syminfo.tickerid,
          deltaLtf,
          [open, close, volume],
          ignore_invalid_timeframe=true)

int intrabarCount = array.size(ltfVolumeArray)

float ltfBuyVolume = 0.0
float ltfSellVolume = 0.0
int bullishIntrabars = 0
int bearishIntrabars = 0
int currentBullStreak = 0
int currentBearStreak = 0
int maxBullStreak = 0
int maxBearStreak = 0

if intrabarCount > 0
    for i = 0 to intrabarCount - 1
        float io = array.get(ltfOpenArray, i)
        float ic = array.get(ltfCloseArray, i)
        float iv = nz(array.get(ltfVolumeArray, i), 0.0)
        float previousIntrabarClose = i > 0 ? array.get(ltfCloseArray, i - 1) : io

        int intrabarDirection =
             ic > io
             ? 1
             : ic < io
             ? -1
             : ic > previousIntrabarClose
             ? 1
             : ic < previousIntrabarClose
             ? -1
             : 0

        if intrabarDirection > 0
            ltfBuyVolume += iv
            bullishIntrabars += 1
            currentBullStreak += 1
            currentBearStreak := 0
            maxBullStreak := math.max(maxBullStreak, currentBullStreak)
        else if intrabarDirection < 0
            ltfSellVolume += iv
            bearishIntrabars += 1
            currentBearStreak += 1
            currentBullStreak := 0
            maxBearStreak := math.max(maxBearStreak, currentBearStreak)
        else
            ltfBuyVolume += iv * 0.5
            ltfSellVolume += iv * 0.5
            currentBullStreak := 0
            currentBearStreak := 0

bool hasLtfDelta = intrabarCount >= minDeltaIntrabars and ltfBuyVolume + ltfSellVolume > 0

float estimatedBuyVolume = volume * candleCloseLocation
float estimatedSellVolume = math.max(volume - estimatedBuyVolume, 0.0)

float deltaBuyVolume =
     hasLtfDelta
     ? ltfBuyVolume
     : deltaFallbackToBar
     ? estimatedBuyVolume
     : na

float deltaSellVolume =
     hasLtfDelta
     ? ltfSellVolume
     : deltaFallbackToBar
     ? estimatedSellVolume
     : na

float deltaTotalVolume = deltaBuyVolume + deltaSellVolume
float deltaNet = deltaBuyVolume - deltaSellVolume

float deltaPct =
     not na(deltaTotalVolume) and deltaTotalVolume > 0
     ? deltaNet / deltaTotalVolume * 100.0
     : na

bool deltaReady = not na(deltaPct)

int directionalIntrabars = bullishIntrabars + bearishIntrabars

float bullishSubbarShare =
     hasLtfDelta and directionalIntrabars > 0
     ? float(bullishIntrabars) / float(directionalIntrabars)
     : close > open
     ? 1.0
     : close < open
     ? 0.0
     : 0.5

float bearishSubbarShare = 1.0 - bullishSubbarShare

bool bullStackOk = not requireStackedSubbars or not hasLtfDelta or maxBullStreak >= minStackedSubbars
bool bearStackOk = not requireStackedSubbars or not hasLtfDelta or maxBearStreak >= minStackedSubbars

bool bullDirectionalDeltaNow =
     deltaReady and
     deltaPct >= deltaPctThreshold and
     bullishSubbarShare >= directionalSubbarShare and
     bullStackOk

bool bearDirectionalDeltaNow =
     deltaReady and
     deltaPct <= -deltaPctThreshold and
     bearishSubbarShare >= directionalSubbarShare and
     bearStackOk

// ═══════════════════════════════════════════════════════════════════
// ABSORPTION / DELTA DIVERGENCE
// ═══════════════════════════════════════════════════════════════════

float previousPriceLow = ta.lowest(low[1], absorptionLookback)
float previousPriceHigh = ta.highest(high[1], absorptionLookback)
float previousDeltaLow = ta.lowest(deltaPct[1], absorptionLookback)
float previousDeltaHigh = ta.highest(deltaPct[1], absorptionLookback)

bool bullAbsorptionNow =
     deltaReady and
     not na(previousPriceLow) and
     low < previousPriceLow and
     deltaPct <= -absorptionDeltaPct and
     candleCloseLocation >= absorptionCloseLocation

bool bearAbsorptionNow =
     deltaReady and
     not na(previousPriceHigh) and
     high > previousPriceHigh and
     deltaPct >= absorptionDeltaPct and
     candleCloseLocation <= 1.0 - absorptionCloseLocation

bool bullDeltaDivergenceNow =
     deltaReady and
     not na(previousPriceLow) and
     not na(previousDeltaLow) and
     low < previousPriceLow and
     deltaPct > previousDeltaLow + divergenceDeltaImprovement and
     candleCloseLocation >= 0.50

bool bearDeltaDivergenceNow =
     deltaReady and
     not na(previousPriceHigh) and
     not na(previousDeltaHigh) and
     high > previousPriceHigh and
     deltaPct < previousDeltaHigh - divergenceDeltaImprovement and
     candleCloseLocation <= 0.50

bool bullAbsorptionEvent = bullAbsorptionNow or bullDeltaDivergenceNow
bool bearAbsorptionEvent = bearAbsorptionNow or bearDeltaDivergenceNow

// ═══════════════════════════════════════════════════════════════════
// GLOBAL BEHAVIOR BUFFER
//
// These timestamps allow delta / RVOL / absorption to happen BEFORE
// the POC/sweep setup begins without forcing a rigid event sequence.
// ═══════════════════════════════════════════════════════════════════

var int lastBullDirectionalDeltaBar = na
var int lastBearDirectionalDeltaBar = na
var int lastBullAbsorptionBar = na
var int lastBearAbsorptionBar = na
var int lastRelativeVolumeBar = na

if bullDirectionalDeltaNow
    lastBullDirectionalDeltaBar := bar_index

if bearDirectionalDeltaNow
    lastBearDirectionalDeltaBar := bar_index

if bullAbsorptionEvent
    lastBullAbsorptionBar := bar_index

if bearAbsorptionEvent
    lastBearAbsorptionBar := bar_index

// ═══════════════════════════════════════════════════════════════════
// RELATIVE VOLUME
// ═══════════════════════════════════════════════════════════════════

float relativeVolumeAverage = ta.sma(volume, relativeVolLen)

float relativeVolume =
     not na(relativeVolumeAverage) and relativeVolumeAverage > 0
     ? volume / relativeVolumeAverage
     : na

bool relativeVolumeNow = not na(relativeVolume) and relativeVolume >= relativeVolMin

if relativeVolumeNow
    lastRelativeVolumeBar := bar_index

// ═══════════════════════════════════════════════════════════════════
// LIQUIDITY SWEEP ENGINE
// ═══════════════════════════════════════════════════════════════════

float sweepCandleRange = high - low
float sweepCandleBody = math.abs(close - open)
float sweepUpperWick = high - math.max(open, close)
float sweepLowerWick = math.min(open, close) - low

float sweepBodyPercent = sweepCandleRange > 0 ? sweepCandleBody / sweepCandleRange : 0.0
float sweepUpperWickPercent = sweepCandleRange > 0 ? sweepUpperWick / sweepCandleRange : 0.0
float sweepLowerWickPercent = sweepCandleRange > 0 ? sweepLowerWick / sweepCandleRange : 0.0

float sweepMinDistancePx = atr > 0 ? sweepMinDistanceAtr * atr : na
float sweepMinRangePx = atr > 0 ? sweepMinRangeAtr * atr : na

float sweepPivotHigh = ta.pivothigh(high, sweepPivotLength, sweepPivotLength)
float sweepPivotLow = ta.pivotlow(low, sweepPivotLength, sweepPivotLength)

var float[] storedHighLevels = array.new_float()
var int[] storedHighBars = array.new_int()
var float[] storedLowLevels = array.new_float()
var int[] storedLowBars = array.new_int()

if not na(sweepPivotHigh)
    array.push(storedHighLevels, sweepPivotHigh)
    array.push(storedHighBars, bar_index - sweepPivotLength)
    if array.size(storedHighLevels) > sweepStoredLevels
        array.shift(storedHighLevels)
        array.shift(storedHighBars)

if not na(sweepPivotLow)
    array.push(storedLowLevels, sweepPivotLow)
    array.push(storedLowBars, bar_index - sweepPivotLength)
    if array.size(storedLowLevels) > sweepStoredLevels
        array.shift(storedLowLevels)
        array.shift(storedLowBars)

float sweptHighLevel = na
float sweptLowLevel = na

int highLevelCount = array.size(storedHighLevels)
if highLevelCount > 0
    for offset = 0 to highLevelCount - 1
        int i = highLevelCount - 1 - offset
        float level = array.get(storedHighLevels, i)
        int levelBar = array.get(storedHighBars, i)
        if levelBar < bar_index and high > level and not na(sweepMinDistancePx) and high - level >= sweepMinDistancePx
            sweptHighLevel := level
            break

int lowLevelCount = array.size(storedLowLevels)
if lowLevelCount > 0
    for offset = 0 to lowLevelCount - 1
        int i = lowLevelCount - 1 - offset
        float level = array.get(storedLowLevels, i)
        int levelBar = array.get(storedLowBars, i)
        if levelBar < bar_index and low < level and not na(sweepMinDistancePx) and level - low >= sweepMinDistancePx
            sweptLowLevel := level
            break

bool bearishSweepReclaim =
     not na(sweptHighLevel) and
     (
          sweepReclaimRule == "Close Back Inside"
          ? close < sweptHighLevel
          : close < sweptHighLevel and close < (high + low) / 2.0
     )

bool bullishSweepReclaim =
     not na(sweptLowLevel) and
     (
          sweepReclaimRule == "Close Back Inside"
          ? close > sweptLowLevel
          : close > sweptLowLevel and close > (high + low) / 2.0
     )

bool validSellSweep =
     not na(sweptHighLevel) and
     bearishSweepReclaim and
     sweepUpperWickPercent >= sweepMinWickPercent and
     sweepBodyPercent <= sweepMaxBodyPercent and
     not na(sweepMinRangePx) and
     sweepCandleRange >= sweepMinRangePx

bool validBuySweep =
     not na(sweptLowLevel) and
     bullishSweepReclaim and
     sweepLowerWickPercent >= sweepMinWickPercent and
     sweepBodyPercent <= sweepMaxBodyPercent and
     not na(sweepMinRangePx) and
     sweepCandleRange >= sweepMinRangePx

// ═══════════════════════════════════════════════════════════════════
// SESSION PROFILE ENGINE
// ═══════════════════════════════════════════════════════════════════

var float[] profileVolumes = array.new_float(profileRows, 0.0)
var float[] profileBuyVolumes = array.new_float(profileRows, 0.0)
var float[] profileSellVolumes = array.new_float(profileRows, 0.0)

var float profileHigh = na
var float profileLow = na
var int profileStartBar = na
var float pocPrice = na
var float vahPrice = na
var float valPrice = na
var int profilePocIndex = na
var int profileVahIndex = na
var int profileValIndex = na
var float profileRowInterval = na
var float profileMaxVolume = na

bool customProfileInSession = not na(time(timeframe.period, profileSession, sessionTimezone))
bool profileInSession = profileSessionType == "All" ? true : customProfileInSession
bool profileNewDay = not barstate.isfirst and dayIdentifier != dayIdentifier[1]

bool profileStartNow =
     profileSessionType == "All"
     ? (barstate.isfirst or profileNewDay)
     : (profileInSession and (barstate.isfirst or not profileInSession[1]))

bool profileFinalizeNow =
     profileSessionType == "All"
     ? profileNewDay
     : (not profileInSession and profileInSession[1])

// ═══════════════════════════════════════════════════════════════════
// HISTORICAL PROFILE OBJECT STORAGE
// ═══════════════════════════════════════════════════════════════════

var box[] historicalProfileBoxes = array.new_box()
var line[] historicalProfileLines = array.new_line()
var int[] historicalSessionBoxCounts = array.new_int()
var int[] historicalSessionLineCounts = array.new_int()

// ═══════════════════════════════════════════════════════════════════
// FINALIZE COMPLETED PROFILE
// ═══════════════════════════════════════════════════════════════════

if profileFinalizeNow and not na(profileStartBar) and not na(profileMaxVolume) and profileMaxVolume > 0 and not na(profileRowInterval)
    int finalEndBar = bar_index - 1

    if showHistoricalProfiles
        int availableSessionBars = math.max(2, finalEndBar - profileStartBar + 1)
        int maxHistogramWidth = math.min(profileWidthBars, availableSessionBars)
        int newBoxCount = 0
        int newLineCount = 0

        for row = 0 to profileRows - 1
            float rowVolume = array.get(profileVolumes, row)
            float rowBuyVolume = array.get(profileBuyVolumes, row)
            float rowBottom = profileLow + profileRowInterval * row
            float rowTop = profileLow + profileRowInterval * (row + 1)
            bool rowInsideVA = row >= profileValIndex and row <= profileVahIndex
            bool rowIsPoc = row == profilePocIndex

            if rowVolume > 0
                int totalWidth = math.max(1, int(math.round(rowVolume / profileMaxVolume * maxHistogramWidth)))
                float buyShare = rowBuyVolume / rowVolume
                buyShare := math.max(0.0, math.min(1.0, buyShare))
                int buyWidth = int(math.round(totalWidth * buyShare))
                buyWidth := math.max(0, math.min(totalWidth, buyWidth))
                int sellWidth = totalWidth - buyWidth

                int profileLeftEdge = profileAlignment == "Left" ? profileStartBar : finalEndBar - totalWidth
                int buyLeft = profileLeftEdge
                int buyRight = profileLeftEdge + buyWidth
                int sellLeft = buyRight
                int sellRight = profileLeftEdge + totalWidth

                int transparency = rowIsPoc ? 28 : rowInsideVA ? 62 : 84
                color borderColor = rowIsPoc ? C_ORANGE : color.new(C_GRAY, 100)

                if buyWidth > 0
                    box buyBox = box.new(buyLeft, rowTop, buyRight, rowBottom, border_color=borderColor, border_width=1, bgcolor=color.new(C_BLUE, transparency))
                    array.push(historicalProfileBoxes, buyBox)
                    newBoxCount += 1

                if sellWidth > 0
                    box sellBox = box.new(sellLeft, rowTop, sellRight, rowBottom, border_color=borderColor, border_width=1, bgcolor=color.new(C_SELL, transparency))
                    array.push(historicalProfileBoxes, sellBox)
                    newBoxCount += 1

        if showProfileLevels
            line pocLine = line.new(profileStartBar, pocPrice, finalEndBar, pocPrice, color=color.new(C_ORANGE, 10), width=2)
            line vahLine = line.new(profileStartBar, vahPrice, finalEndBar, vahPrice, color=color.new(C_GRAY, 55), style=line.style_dashed)
            line valLine = line.new(profileStartBar, valPrice, finalEndBar, valPrice, color=color.new(C_GRAY, 55), style=line.style_dashed)
            array.push(historicalProfileLines, pocLine)
            array.push(historicalProfileLines, vahLine)
            array.push(historicalProfileLines, valLine)
            newLineCount += 3

        array.push(historicalSessionBoxCounts, newBoxCount)
        array.push(historicalSessionLineCounts, newLineCount)

        while array.size(historicalSessionBoxCounts) > historicalProfileCount
            int boxesToDelete = array.shift(historicalSessionBoxCounts)
            int linesToDelete = array.shift(historicalSessionLineCounts)

            if boxesToDelete > 0
                for i = 0 to boxesToDelete - 1
                    if array.size(historicalProfileBoxes) > 0
                        box.delete(array.shift(historicalProfileBoxes))

            if linesToDelete > 0
                for i = 0 to linesToDelete - 1
                    if array.size(historicalProfileLines) > 0
                        line.delete(array.shift(historicalProfileLines))

// ═══════════════════════════════════════════════════════════════════
// START NEW PROFILE
// ═══════════════════════════════════════════════════════════════════

if profileStartNow
    array.fill(profileVolumes, 0.0)
    array.fill(profileBuyVolumes, 0.0)
    array.fill(profileSellVolumes, 0.0)
    profileHigh := high
    profileLow := low
    profileStartBar := bar_index
    pocPrice := na
    vahPrice := na
    valPrice := na
    profilePocIndex := na
    profileVahIndex := na
    profileValIndex := na
    profileRowInterval := na
    profileMaxVolume := na

// ═══════════════════════════════════════════════════════════════════
// DEVELOP CURRENT PROFILE
// ═══════════════════════════════════════════════════════════════════

if profileInSession
    profileHigh := math.max(nz(profileHigh, high), high)
    profileLow := math.min(nz(profileLow, low), low)

if barstate.isconfirmed and profileInSession and not na(profileStartBar) and not na(profileHigh) and not na(profileLow)
    int profileLookback = bar_index - profileStartBar + 1
    float profilePriceRange = profileHigh - profileLow

    if profilePriceRange > 0 and profileLookback > 0
        profileRowInterval := profilePriceRange / profileRows
        array.fill(profileVolumes, 0.0)
        array.fill(profileBuyVolumes, 0.0)
        array.fill(profileSellVolumes, 0.0)

        for i = 0 to profileLookback - 1
            int startRow = int(math.floor((low[i] - profileLow) / profileRowInterval))
            int endRow = int(math.floor((high[i] - profileLow) / profileRowInterval))
            startRow := math.max(0, math.min(profileRows - 1, startRow))
            endRow := math.max(0, math.min(profileRows - 1, endRow))
            int rowsTouched = endRow - startRow + 1
            float sourceBarRange = high[i] - low[i]

            float historicalBuy = deltaBuyVolume[i]
            float historicalSell = deltaSellVolume[i]
            float historicalDeltaTotal = historicalBuy + historicalSell

            float historicalBuyShare =
                 not na(historicalDeltaTotal) and historicalDeltaTotal > 0
                 ? historicalBuy / historicalDeltaTotal
                 : 0.5

            historicalBuyShare := math.max(0.0, math.min(1.0, historicalBuyShare))

            for row = startRow to endRow
                float rowBottom = profileLow + profileRowInterval * row
                float rowTop = profileLow + profileRowInterval * (row + 1)
                float overlapTop = math.min(high[i], rowTop)
                float overlapBottom = math.max(low[i], rowBottom)
                float overlapRange = math.max(0.0, overlapTop - overlapBottom)

                float volumeToAdd =
                     sourceBarRange > 0
                     ? volume[i] * (overlapRange / sourceBarRange)
                     : volume[i] / rowsTouched

                float buyToAdd = volumeToAdd * historicalBuyShare
                float sellToAdd = volumeToAdd - buyToAdd

                array.set(profileVolumes, row, array.get(profileVolumes, row) + volumeToAdd)
                array.set(profileBuyVolumes, row, array.get(profileBuyVolumes, row) + buyToAdd)
                array.set(profileSellVolumes, row, array.get(profileSellVolumes, row) + sellToAdd)

        profileMaxVolume := array.max(profileVolumes)

        if profileMaxVolume > 0
            profilePocIndex := array.indexof(profileVolumes, profileMaxVolume)
            pocPrice := profileLow + profileRowInterval * (profilePocIndex + 0.5)

            float totalProfileVolume = array.sum(profileVolumes)
            float targetVaVolume = totalProfileVolume * valueAreaPct / 100.0
            int workingVahIndex = profilePocIndex
            int workingValIndex = profilePocIndex
            float accumulatedVolume = array.get(profileVolumes, profilePocIndex)

            while accumulatedVolume < targetVaVolume and (workingVahIndex < profileRows - 1 or workingValIndex > 0)
                float volumeAbove = workingVahIndex < profileRows - 1 ? array.get(profileVolumes, workingVahIndex + 1) : 0.0
                float volumeBelow = workingValIndex > 0 ? array.get(profileVolumes, workingValIndex - 1) : 0.0

                if volumeAbove > volumeBelow and workingVahIndex < profileRows - 1
                    workingVahIndex += 1
                    accumulatedVolume += volumeAbove
                else if workingValIndex > 0
                    workingValIndex -= 1
                    accumulatedVolume += volumeBelow
                else if workingVahIndex < profileRows - 1
                    workingVahIndex += 1
                    accumulatedVolume += volumeAbove
                else
                    break

            profileVahIndex := workingVahIndex
            profileValIndex := workingValIndex
            vahPrice := profileLow + profileRowInterval * (profileVahIndex + 0.5)
            valPrice := profileLow + profileRowInterval * (profileValIndex + 0.5)

// ═══════════════════════════════════════════════════════════════════
// POC MIGRATION
// ═══════════════════════════════════════════════════════════════════

int profileAge = not na(profileStartBar) ? bar_index - profileStartBar : 0

bool bullishPocMigration =
     profileAge >= pocMigrationLookback and
     not na(pocPrice[pocMigrationLookback]) and
     pocPrice > pocPrice[pocMigrationLookback]

bool bearishPocMigration =
     profileAge >= pocMigrationLookback and
     not na(pocPrice[pocMigrationLookback]) and
     pocPrice < pocPrice[pocMigrationLookback]

// ═══════════════════════════════════════════════════════════════════
// STATE MACHINE
// ═══════════════════════════════════════════════════════════════════

const int STATE_IDLE  = 0
const int STATE_BUILD = 1
const int STATE_READY = 2

var int longState = STATE_IDLE
var int shortState = STATE_IDLE
var int longSetupStartBar = na
var int shortSetupStartBar = na
var float longAnchorPoc = na
var float shortAnchorPoc = na
var int longPocTouchBar = na
var int shortPocTouchBar = na
var int longAnchoredSweepBar = na
var int shortAnchoredSweepBar = na
var int longReclaimBar = na
var int shortReclaimBar = na
var int longDirectionalDeltaBar = na
var int shortDirectionalDeltaBar = na
var int longAbsorptionBar = na
var int shortAbsorptionBar = na
var int longRelativeVolumeBar = na
var int shortRelativeVolumeBar = na
var float longSweepMid = na
var float shortSweepMid = na
var float longSetupStructure = na
var float shortSetupStructure = na

bool flat = strategy.position_size == 0

float pocTolerance = atr * pocAnchorDistanceAtr
bool pocAvailable = profileInSession and not na(pocPrice)

bool longPocTouchNow =
     pocAvailable and
     (math.abs(low - pocPrice) <= pocTolerance or math.abs(close - pocPrice) <= pocTolerance)

bool shortPocTouchNow =
     pocAvailable and
     (math.abs(high - pocPrice) <= pocTolerance or math.abs(close - pocPrice) <= pocTolerance)

// New auction context resets both hypotheses.
if profileStartNow and not barstate.isfirst
    longState := STATE_IDLE
    shortState := STATE_IDLE
    longSetupStartBar := na
    shortSetupStartBar := na
    longAnchorPoc := na
    shortAnchorPoc := na
    longPocTouchBar := na
    shortPocTouchBar := na
    longAnchoredSweepBar := na
    shortAnchoredSweepBar := na
    longReclaimBar := na
    shortReclaimBar := na
    longDirectionalDeltaBar := na
    shortDirectionalDeltaBar := na
    longAbsorptionBar := na
    shortAbsorptionBar := na
    longRelativeVolumeBar := na
    shortRelativeVolumeBar := na
    longSweepMid := na
    shortSweepMid := na
    longSetupStructure := na
    shortSetupStructure := na

// ═══════════════════════════════════════════════════════════════════
// SEED LONG HYPOTHESIS
// ═══════════════════════════════════════════════════════════════════

bool longSeedEvent =
     flat and
     pocAvailable and
     (
          longPocTouchNow or
          (validBuySweep and math.abs(low - pocPrice) <= pocTolerance)
     )

if longState == STATE_IDLE and longSeedEvent
    longState := STATE_BUILD
    longSetupStartBar := bar_index
    // Freeze the developing POC at setup initiation.
    longAnchorPoc := pocPrice

    if longPocTouchNow
        longPocTouchBar := bar_index

    // Import recent behavioral evidence.
    if f_recent(lastBullDirectionalDeltaBar, setupWindowBars)
        longDirectionalDeltaBar := lastBullDirectionalDeltaBar

    if f_recent(lastBullAbsorptionBar, setupWindowBars)
        longAbsorptionBar := lastBullAbsorptionBar

    if f_recent(lastRelativeVolumeBar, setupWindowBars)
        longRelativeVolumeBar := lastRelativeVolumeBar

// ═══════════════════════════════════════════════════════════════════
// SEED SHORT HYPOTHESIS
// ═══════════════════════════════════════════════════════════════════

bool shortSeedEvent =
     flat and
     pocAvailable and
     (
          shortPocTouchNow or
          (validSellSweep and math.abs(high - pocPrice) <= pocTolerance)
     )

if shortState == STATE_IDLE and shortSeedEvent
    shortState := STATE_BUILD
    shortSetupStartBar := bar_index
    shortAnchorPoc := pocPrice

    if shortPocTouchNow
        shortPocTouchBar := bar_index

    if f_recent(lastBearDirectionalDeltaBar, setupWindowBars)
        shortDirectionalDeltaBar := lastBearDirectionalDeltaBar

    if f_recent(lastBearAbsorptionBar, setupWindowBars)
        shortAbsorptionBar := lastBearAbsorptionBar

    if f_recent(lastRelativeVolumeBar, setupWindowBars)
        shortRelativeVolumeBar := lastRelativeVolumeBar

// ═══════════════════════════════════════════════════════════════════
// UPDATE LONG STATE
// ═══════════════════════════════════════════════════════════════════

if longState != STATE_IDLE
    if math.abs(low - longAnchorPoc) <= pocTolerance or math.abs(close - longAnchorPoc) <= pocTolerance
        longPocTouchBar := bar_index

    if bullDirectionalDeltaNow
        longDirectionalDeltaBar := bar_index

    if bullAbsorptionEvent
        longAbsorptionBar := bar_index

    if relativeVolumeNow
        longRelativeVolumeBar := bar_index

    if validBuySweep and math.abs(low - longAnchorPoc) <= pocTolerance
        longAnchoredSweepBar := bar_index
        longSetupStructure := sweptLowLevel
        longSweepMid := (high + low) / 2.0

    bool longBodyReclaimOk = not sweepRequireBullishBody or close > open
    bool longMidReclaimOk = not sweepRequireMidlineBreak or (not na(longSweepMid) and close > longSweepMid)

    bool longReclaimNow =
         not na(longAnchoredSweepBar) and
         close > longAnchorPoc and
         longBodyReclaimOk and
         longMidReclaimOk

    if longReclaimNow
        longReclaimBar := bar_index

// ═══════════════════════════════════════════════════════════════════
// UPDATE SHORT STATE
// ═══════════════════════════════════════════════════════════════════

if shortState != STATE_IDLE
    if math.abs(high - shortAnchorPoc) <= pocTolerance or math.abs(close - shortAnchorPoc) <= pocTolerance
        shortPocTouchBar := bar_index

    if bearDirectionalDeltaNow
        shortDirectionalDeltaBar := bar_index

    if bearAbsorptionEvent
        shortAbsorptionBar := bar_index

    if relativeVolumeNow
        shortRelativeVolumeBar := bar_index

    if validSellSweep and math.abs(high - shortAnchorPoc) <= pocTolerance
        shortAnchoredSweepBar := bar_index
        shortSetupStructure := sweptHighLevel
        shortSweepMid := (high + low) / 2.0

    bool shortBodyReclaimOk = not sweepRequireBearishBody or close < open
    bool shortMidReclaimOk = not sweepRequireMidlineBreak or (not na(shortSweepMid) and close < shortSweepMid)

    bool shortReclaimNow =
         not na(shortAnchoredSweepBar) and
         close < shortAnchorPoc and
         shortBodyReclaimOk and
         shortMidReclaimOk

    if shortReclaimNow
        shortReclaimBar := bar_index

// ═══════════════════════════════════════════════════════════════════
// RECENT STATE EVIDENCE
// ═══════════════════════════════════════════════════════════════════

bool longPocRecent = f_recent(longPocTouchBar, setupWindowBars)
bool shortPocRecent = f_recent(shortPocTouchBar, setupWindowBars)
bool longSweepRecent = f_recent(longAnchoredSweepBar, setupWindowBars)
bool shortSweepRecent = f_recent(shortAnchoredSweepBar, setupWindowBars)
bool longReclaimRecent = f_recent(longReclaimBar, setupWindowBars)
bool shortReclaimRecent = f_recent(shortReclaimBar, setupWindowBars)
bool longDirectionalDeltaRecent = f_recent(longDirectionalDeltaBar, setupWindowBars)
bool shortDirectionalDeltaRecent = f_recent(shortDirectionalDeltaBar, setupWindowBars)
bool longAbsorptionRecent = f_recent(longAbsorptionBar, setupWindowBars)
bool shortAbsorptionRecent = f_recent(shortAbsorptionBar, setupWindowBars)
bool longRelativeVolumeRecent = f_recent(longRelativeVolumeBar, setupWindowBars)
bool shortRelativeVolumeRecent = f_recent(shortRelativeVolumeBar, setupWindowBars)

// ═══════════════════════════════════════════════════════════════════
// DELTA STATE
// ═══════════════════════════════════════════════════════════════════

bool longDeltaStateOk =
     deltaConfirmationMode == "Directional"
     ? longDirectionalDeltaRecent
     : deltaConfirmationMode == "Directional OR Absorption"
     ? (longDirectionalDeltaRecent or longAbsorptionRecent)
     : (longDirectionalDeltaRecent and longAbsorptionRecent)

bool shortDeltaStateOk =
     deltaConfirmationMode == "Directional"
     ? shortDirectionalDeltaRecent
     : deltaConfirmationMode == "Directional OR Absorption"
     ? (shortDirectionalDeltaRecent or shortAbsorptionRecent)
     : (shortDirectionalDeltaRecent and shortAbsorptionRecent)

bool longRvolStateOk = not useRelativeVolumeConfirm or longRelativeVolumeRecent
bool shortRvolStateOk = not useRelativeVolumeConfirm or shortRelativeVolumeRecent
bool longPocMigrationOk = not usePocMigrationConfirm or bullishPocMigration
bool shortPocMigrationOk = not usePocMigrationConfirm or bearishPocMigration

bool longEvidenceReady =
     longPocRecent and
     longSweepRecent and
     longReclaimRecent and
     longDeltaStateOk and
     longRvolStateOk and
     longPocMigrationOk

bool shortEvidenceReady =
     shortPocRecent and
     shortSweepRecent and
     shortReclaimRecent and
     shortDeltaStateOk and
     shortRvolStateOk and
     shortPocMigrationOk

if longState == STATE_BUILD and longEvidenceReady
    longState := STATE_READY

if shortState == STATE_BUILD and shortEvidenceReady
    shortState := STATE_READY

// ═══════════════════════════════════════════════════════════════════
// STATE INVALIDATION
// ═══════════════════════════════════════════════════════════════════

int longSetupAge = na(longSetupStartBar) ? 0 : bar_index - longSetupStartBar
int shortSetupAge = na(shortSetupStartBar) ? 0 : bar_index - shortSetupStartBar

bool longInvalidated =
     longState != STATE_IDLE and
     (
          longSetupAge > setupWindowBars or
          profileFinalizeNow or
          (invalidateOnOppositeSweep and validSellSweep) or
          (not na(longAnchorPoc) and close < longAnchorPoc - atr * setupInvalidationAtr)
     )

bool shortInvalidated =
     shortState != STATE_IDLE and
     (
          shortSetupAge > setupWindowBars or
          profileFinalizeNow or
          (invalidateOnOppositeSweep and validBuySweep) or
          (not na(shortAnchorPoc) and close > shortAnchorPoc + atr * setupInvalidationAtr)
     )

if longInvalidated
    longState := STATE_IDLE
    longSetupStartBar := na
    longAnchorPoc := na
    longPocTouchBar := na
    longAnchoredSweepBar := na
    longReclaimBar := na
    longDirectionalDeltaBar := na
    longAbsorptionBar := na
    longRelativeVolumeBar := na
    longSweepMid := na
    longSetupStructure := na

if shortInvalidated
    shortState := STATE_IDLE
    shortSetupStartBar := na
    shortAnchorPoc := na
    shortPocTouchBar := na
    shortAnchoredSweepBar := na
    shortReclaimBar := na
    shortDirectionalDeltaBar := na
    shortAbsorptionBar := na
    shortRelativeVolumeBar := na
    shortSweepMid := na
    shortSetupStructure := na

// ═══════════════════════════════════════════════════════════════════
// STOP / POSITION SIZE
// ═══════════════════════════════════════════════════════════════════

float rawLongStop = not na(longSetupStructure) ? longSetupStructure - atr * stopBufferATR : close - atr * maxStopATR
float cappedLongStop = close - atr * maxStopATR
float candidateLongStop = math.max(rawLongStop, cappedLongStop)

float rawShortStop = not na(shortSetupStructure) ? shortSetupStructure + atr * stopBufferATR : close + atr * maxStopATR
float cappedShortStop = close + atr * maxStopATR
float candidateShortStop = math.min(rawShortStop, cappedShortStop)

float candidateLongRisk = close - candidateLongStop
float candidateShortRisk = candidateShortStop - close

bool longRiskValid = candidateLongRisk > syminfo.mintick
bool shortRiskValid = candidateShortRisk > syminfo.mintick

float pointValue = not na(syminfo.pointvalue) and syminfo.pointvalue > 0 ? syminfo.pointvalue : 1.0
bool useDollarSizing = sizingMode == "Risk %" or sizingMode == "Risk $"

float riskBudget = sizingMode == "Risk %" ? strategy.equity * riskPerTradePct / 100.0 : maxRiskDollars

float longRiskQty =
     useDollarSizing and longRiskValid
     ? math.floor(riskBudget / (candidateLongRisk * pointValue))
     : baseQty

float shortRiskQty =
     useDollarSizing and shortRiskValid
     ? math.floor(riskBudget / (candidateShortRisk * pointValue))
     : baseQty

bool longSizePass = longRiskValid and (not useDollarSizing or longRiskQty >= 1.0)
bool shortSizePass = shortRiskValid and (not useDollarSizing or shortRiskQty >= 1.0)

float longOrderQty = useDollarSizing ? longRiskQty : baseQty
float shortOrderQty = useDollarSizing ? shortRiskQty : baseQty

// ═══════════════════════════════════════════════════════════════════
// FINAL ENTRY
// ═══════════════════════════════════════════════════════════════════

bool longEntry =
     barstate.isconfirmed and
     flat and
     tradeDirectionLongOk and
     commonLiveGate and
     longState == STATE_READY and
     longRegimeOk and
     longSizePass

bool shortEntry =
     barstate.isconfirmed and
     flat and
     tradeDirectionShortOk and
     commonLiveGate and
     shortState == STATE_READY and
     shortRegimeOk and
     shortSizePass and
     not longEntry

// ═══════════════════════════════════════════════════════════════════
// BLOCKERS
// ═══════════════════════════════════════════════════════════════════

string longBlocker =
     tradeDirection == "Short Only" ? "LONGS OFF" :
     not inTradeSession ? "SESSION" :
     not dailyTradeLimitPass ? "TRADE LIMIT" :
     not cooldownPass ? "COOLDOWN" :
     not entrySpacingPass ? "SPACING" :
     longState == STATE_IDLE ? "WAIT SETUP" :
     not longPocRecent ? "POC" :
     not longSweepRecent ? "SWEEP" :
     not longReclaimRecent ? "RECLAIM" :
     not longDeltaStateOk ? "DELTA" :
     not longRvolStateOk ? "RVOL" :
     not longPocMigrationOk ? "POC SLOPE" :
     not longTrendOk ? "TREND" :
     not longMomentumOk ? "MOMENTUM" :
     not longSizePass ? "SIZE" :
     strategy.position_size != 0 ? "POSITION" :
     "CLEAR"

string shortBlocker =
     tradeDirection == "Long Only" ? "SHORTS OFF" :
     not inTradeSession ? "SESSION" :
     not dailyTradeLimitPass ? "TRADE LIMIT" :
     not cooldownPass ? "COOLDOWN" :
     not entrySpacingPass ? "SPACING" :
     shortState == STATE_IDLE ? "WAIT SETUP" :
     not shortPocRecent ? "POC" :
     not shortSweepRecent ? "SWEEP" :
     not shortReclaimRecent ? "RECLAIM" :
     not shortDeltaStateOk ? "DELTA" :
     not shortRvolStateOk ? "RVOL" :
     not shortPocMigrationOk ? "POC SLOPE" :
     not shortTrendOk ? "TREND" :
     not shortMomentumOk ? "MOMENTUM" :
     not shortSizePass ? "SIZE" :
     strategy.position_size != 0 ? "POSITION" :
     "CLEAR"

// ═══════════════════════════════════════════════════════════════════
// TRADE STATE
// ═══════════════════════════════════════════════════════════════════

var float pendingStop = na
var float entryPrice = na
var float initialRisk = na
var float longStop = na
var float shortStop = na
var float tp1Price = na
var float tp2Price = na
var bool tp1Hit = false
var bool breakEvenActive = false
var bool trailingActive = false
var float highestSinceEntry = na
var float lowestSinceEntry = na
var int entryBar = na

// ═══════════════════════════════════════════════════════════════════
// ORDERS
// ═══════════════════════════════════════════════════════════════════

if longEntry
    pendingStop := candidateLongStop
    tradesToday += 1
    lastEntrySignalBar := bar_index

    strategy.entry("Long", strategy.long, qty=longOrderQty, comment="Ranger V2 Long", alert_message="RANGER_V2_LONG")

    if enableDynamicAlerts
        alert(
             f_alertMsg("BULLISH ENTRY", "Ranger V2", close) +
             "\nFrozen POC: " + f_price(longAnchorPoc) +
             "\nDelta: " + f_number(deltaPct) + "%" +
             "\nRVOL: " + f_number(relativeVolume),
             alert.freq_once_per_bar_close)

    longState := STATE_IDLE
    shortState := STATE_IDLE
    longSetupStartBar := na
    shortSetupStartBar := na
    longPocTouchBar := na
    shortPocTouchBar := na
    longAnchoredSweepBar := na
    shortAnchoredSweepBar := na
    longReclaimBar := na
    shortReclaimBar := na
    longDirectionalDeltaBar := na
    shortDirectionalDeltaBar := na
    longAbsorptionBar := na
    shortAbsorptionBar := na
    longRelativeVolumeBar := na
    shortRelativeVolumeBar := na
    longSweepMid := na
    shortSweepMid := na
    longSetupStructure := na
    shortSetupStructure := na

if shortEntry
    pendingStop := candidateShortStop
    tradesToday += 1
    lastEntrySignalBar := bar_index

    strategy.entry("Short", strategy.short, qty=shortOrderQty, comment="Ranger V2 Short", alert_message="RANGER_V2_SHORT")

    if enableDynamicAlerts
        alert(
             f_alertMsg("BEARISH ENTRY", "Ranger V2", close) +
             "\nFrozen POC: " + f_price(shortAnchorPoc) +
             "\nDelta: " + f_number(deltaPct) + "%" +
             "\nRVOL: " + f_number(relativeVolume),
             alert.freq_once_per_bar_close)

    longState := STATE_IDLE
    shortState := STATE_IDLE
    longSetupStartBar := na
    shortSetupStartBar := na
    longPocTouchBar := na
    shortPocTouchBar := na
    longAnchoredSweepBar := na
    shortAnchoredSweepBar := na
    longReclaimBar := na
    shortReclaimBar := na
    longDirectionalDeltaBar := na
    shortDirectionalDeltaBar := na
    longAbsorptionBar := na
    shortAbsorptionBar := na
    longRelativeVolumeBar := na
    shortRelativeVolumeBar := na
    longSweepMid := na
    shortSweepMid := na
    longSetupStructure := na
    shortSetupStructure := na

// ═══════════════════════════════════════════════════════════════════
// FILL DETECTION
// ═══════════════════════════════════════════════════════════════════

bool longFill = strategy.position_size > 0 and strategy.position_size[1] <= 0
bool shortFill = strategy.position_size < 0 and strategy.position_size[1] >= 0

if longFill
    entryPrice := strategy.position_avg_price
    longStop := not na(pendingStop) ? pendingStop : entryPrice - atr * maxStopATR
    initialRisk := entryPrice - longStop
    tp1Price := entryPrice + initialRisk * tp1R
    tp2Price := entryPrice + initialRisk * tp2R
    highestSinceEntry := high
    lowestSinceEntry := low
    entryBar := bar_index
    tp1Hit := false
    breakEvenActive := false
    trailingActive := false

if shortFill
    entryPrice := strategy.position_avg_price
    shortStop := not na(pendingStop) ? pendingStop : entryPrice + atr * maxStopATR
    initialRisk := shortStop - entryPrice
    tp1Price := entryPrice - initialRisk * tp1R
    tp2Price := entryPrice - initialRisk * tp2R
    highestSinceEntry := high
    lowestSinceEntry := low
    entryBar := bar_index
    tp1Hit := false
    breakEvenActive := false
    trailingActive := false

// ═══════════════════════════════════════════════════════════════════
// TP1
// ═══════════════════════════════════════════════════════════════════

if strategy.position_size > 0 and not tp1Hit and not na(tp1Price) and high >= tp1Price
    tp1Hit := true

if strategy.position_size < 0 and not tp1Hit and not na(tp1Price) and low <= tp1Price
    tp1Hit := true

// ═══════════════════════════════════════════════════════════════════
// BREAK EVEN + DELTA-AWARE TRAILING
// ═══════════════════════════════════════════════════════════════════

float breakEvenOffset = breakEvenOffsetTicks * syminfo.mintick

if strategy.position_size > 0
    highestSinceEntry := math.max(nz(highestSinceEntry, high), high)
    lowestSinceEntry := math.min(nz(lowestSinceEntry, low), low)

    if tp1Hit and useBreakEven
        longStop := math.max(longStop, entryPrice + breakEvenOffset)
        breakEvenActive := true

    if tp1Hit and useTrail
        bool longOrderFlowFading = not deltaReady or deltaPct <= 0
        float trailMultiplier = longOrderFlowFading ? tightTrailATR : looseTrailATR
        float proposedTrail = highestSinceEntry - atr * trailMultiplier
        longStop := math.max(longStop, proposedTrail)
        trailingActive := true

if strategy.position_size < 0
    highestSinceEntry := math.max(nz(highestSinceEntry, high), high)
    lowestSinceEntry := math.min(nz(lowestSinceEntry, low), low)

    if tp1Hit and useBreakEven
        shortStop := math.min(shortStop, entryPrice - breakEvenOffset)
        breakEvenActive := true

    if tp1Hit and useTrail
        bool shortOrderFlowFading = not deltaReady or deltaPct >= 0
        float trailMultiplier = shortOrderFlowFading ? tightTrailATR : looseTrailATR
        float proposedTrail = lowestSinceEntry + atr * trailMultiplier
        shortStop := math.min(shortStop, proposedTrail)
        trailingActive := true

// ═══════════════════════════════════════════════════════════════════
// EXITS
// ═══════════════════════════════════════════════════════════════════

if strategy.position_size > 0
    strategy.exit("L-TP1", from_entry="Long", qty_percent=tp1Qty, stop=longStop, limit=tp1Price, comment_loss="ATR Stop", comment_profit="TP1")
    strategy.exit("L-TP2", from_entry="Long", qty_percent=100.0 - tp1Qty, stop=longStop, limit=tp2Price, comment_loss="ATR Stop", comment_profit="TP2")

if strategy.position_size < 0
    strategy.exit("S-TP1", from_entry="Short", qty_percent=tp1Qty, stop=shortStop, limit=tp1Price, comment_loss="ATR Stop", comment_profit="TP1")
    strategy.exit("S-TP2", from_entry="Short", qty_percent=100.0 - tp1Qty, stop=shortStop, limit=tp2Price, comment_loss="ATR Stop", comment_profit="TP2")

// ═══════════════════════════════════════════════════════════════════
// TIME / SESSION EXIT
// ═══════════════════════════════════════════════════════════════════

int barsInTrade = strategy.position_size != 0 and not na(entryBar) ? bar_index - entryBar : 0

if strategy.position_size != 0 and barsInTrade >= maximumHoldingBars
    strategy.close_all(comment="Time Stop", immediately=true)

if closeAtSessionEnd and sessionEndBar and strategy.position_size != 0
    strategy.close_all(comment="Session Close", immediately=true)

// ═══════════════════════════════════════════════════════════════════
// CLOSED POSITION
// ═══════════════════════════════════════════════════════════════════

bool positionJustClosed = strategy.position_size == 0 and strategy.position_size[1] != 0
bool longPositionClosed = positionJustClosed and strategy.position_size[1] > 0
bool shortPositionClosed = positionJustClosed and strategy.position_size[1] < 0

string lastExitReason = strategy.closedtrades > 0 ? strategy.closedtrades.exit_comment(strategy.closedtrades - 1) : na

if longPositionClosed and enableDynamicAlerts
    alert(
         f_alertMsg("LONG EXIT", "Ranger V2", close) +
         "\nReason: " + (na(lastExitReason) or lastExitReason == "" ? "—" : lastExitReason),
         alert.freq_once_per_bar_close)

if shortPositionClosed and enableDynamicAlerts
    alert(
         f_alertMsg("SHORT EXIT", "Ranger V2", close) +
         "\nReason: " + (na(lastExitReason) or lastExitReason == "" ? "—" : lastExitReason),
         alert.freq_once_per_bar_close)

if positionJustClosed
    pendingStop := na
    entryPrice := na
    initialRisk := na
    longStop := na
    shortStop := na
    tp1Price := na
    tp2Price := na
    highestSinceEntry := na
    lowestSinceEntry := na
    entryBar := na
    tp1Hit := false
    breakEvenActive := false
    trailingActive := false

// ═══════════════════════════════════════════════════════════════════
// TREND RIBBON
// ═══════════════════════════════════════════════════════════════════

bool ribbonBullish = emaFast >= emaSlow
color ribbonColor = ribbonBullish ? C_BLUE : C_SELL

fastPlot = plot(showTrendRibbon ? emaFast : na, "Fast Trend", color=color.new(ribbonColor, 15), linewidth=1)
slowPlot = plot(showTrendRibbon ? emaSlow : na, "Slow Trend", color=color.new(C_GRAY, 55), linewidth=1)

fill(fastPlot, slowPlot, color=showTrendRibbon ? color.new(ribbonColor, 90) : na, title="Trend Ribbon")

color trendBarColor = localBullish ? color.new(C_BLUE, 45) : localBearish ? color.new(C_SELL, 45) : na

barcolor(colorTrendBars ? trendBarColor : na)

// ═══════════════════════════════════════════════════════════════════
// SIGNAL MARKERS
// ═══════════════════════════════════════════════════════════════════

plotshape(showSignals and longEntry, title="Ranger V2 Buy", style=shape.labelup, location=location.belowbar, color=C_BLUE, textcolor=color.white, text="BUY", size=size.tiny)
plotshape(showSignals and shortEntry, title="Ranger V2 Sell", style=shape.labeldown, location=location.abovebar, color=C_SELL, textcolor=color.white, text="SELL", size=size.tiny)
plotshape(showRawSweeps and validBuySweep, title="Bull Sweep", style=shape.diamond, location=location.belowbar, color=color.new(C_BLUE, 25), size=size.tiny)
plotshape(showRawSweeps and validSellSweep, title="Bear Sweep", style=shape.diamond, location=location.abovebar, color=color.new(C_SELL, 25), size=size.tiny)
plotshape(showAbsorptionMarkers and bullAbsorptionEvent, title="Bull Absorption / Divergence", style=shape.circle, location=location.belowbar, color=C_GREEN, size=size.tiny)
plotshape(showAbsorptionMarkers and bearAbsorptionEvent, title="Bear Absorption / Divergence", style=shape.circle, location=location.abovebar, color=C_RED, size=size.tiny)

// ═══════════════════════════════════════════════════════════════════
// CURRENT PROFILE HISTOGRAM
// ═══════════════════════════════════════════════════════════════════

var box[] currentBuyBoxes = array.new_box()
var box[] currentSellBoxes = array.new_box()
var line currentPocLine = na
var line currentVahLine = na
var line currentValLine = na
var label currentPocTag = na
var label currentVahTag = na
var label currentValTag = na

if barstate.islast
    bool currentProfileReady =
         showCurrentProfile and
         profileInSession and
         not na(profileMaxVolume) and
         profileMaxVolume > 0 and
         not na(profileRowInterval) and
         not na(profileStartBar)

    if currentProfileReady
        if array.size(currentBuyBoxes) != profileRows
            if array.size(currentBuyBoxes) > 0
                for i = 0 to array.size(currentBuyBoxes) - 1
                    box.delete(array.get(currentBuyBoxes, i))

            if array.size(currentSellBoxes) > 0
                for i = 0 to array.size(currentSellBoxes) - 1
                    box.delete(array.get(currentSellBoxes, i))

            array.clear(currentBuyBoxes)
            array.clear(currentSellBoxes)

            for i = 0 to profileRows - 1
                box buyPlaceholder = box.new(bar_index, close, bar_index, close, border_color=color.new(C_BLUE, 100), bgcolor=color.new(C_BLUE, 100))
                box sellPlaceholder = box.new(bar_index, close, bar_index, close, border_color=color.new(C_SELL, 100), bgcolor=color.new(C_SELL, 100))
                array.push(currentBuyBoxes, buyPlaceholder)
                array.push(currentSellBoxes, sellPlaceholder)

        int availableCurrentBars = math.max(2, bar_index - profileStartBar + 1)
        int currentMaxWidth = math.min(profileWidthBars, availableCurrentBars)

        for row = 0 to profileRows - 1
            box buyBox = array.get(currentBuyBoxes, row)
            box sellBox = array.get(currentSellBoxes, row)
            float rowVolume = array.get(profileVolumes, row)
            float rowBuyVolume = array.get(profileBuyVolumes, row)
            float rowBottom = profileLow + profileRowInterval * row
            float rowTop = profileLow + profileRowInterval * (row + 1)
            bool rowInsideVA = row >= profileValIndex and row <= profileVahIndex
            bool rowIsPoc = row == profilePocIndex

            int totalWidth = rowVolume > 0 ? math.max(1, int(math.round(rowVolume / profileMaxVolume * currentMaxWidth))) : 0
            float buyShare = rowVolume > 0 ? rowBuyVolume / rowVolume : 0.5
            buyShare := math.max(0.0, math.min(1.0, buyShare))
            int buyWidth = totalWidth > 0 ? int(math.round(totalWidth * buyShare)) : 0
            buyWidth := math.max(0, math.min(totalWidth, buyWidth))
            int sellWidth = totalWidth - buyWidth

            int profileLeftEdge = profileAlignment == "Left" ? profileStartBar : bar_index - totalWidth
            int buyLeft = profileLeftEdge
            int buyRight = profileLeftEdge + buyWidth
            int sellLeft = buyRight
            int sellRight = profileLeftEdge + totalWidth

            int rowTransparency = rowIsPoc ? 25 : rowInsideVA ? 58 : 82
            color rowBorder = rowIsPoc ? C_ORANGE : color.new(C_GRAY, 100)

            box.set_left(buyBox, buyLeft)
            box.set_right(buyBox, buyRight)
            box.set_top(buyBox, rowTop)
            box.set_bottom(buyBox, rowBottom)
            box.set_bgcolor(buyBox, buyWidth > 0 ? color.new(C_BLUE, rowTransparency) : color.new(C_BLUE, 100))
            box.set_border_color(buyBox, rowBorder)

            box.set_left(sellBox, sellLeft)
            box.set_right(sellBox, sellRight)
            box.set_top(sellBox, rowTop)
            box.set_bottom(sellBox, rowBottom)
            box.set_bgcolor(sellBox, sellWidth > 0 ? color.new(C_SELL, rowTransparency) : color.new(C_SELL, 100))
            box.set_border_color(sellBox, rowBorder)

        if showProfileLevels
            int profileRight = bar_index + rightProjectionBars

            if na(currentPocLine)
                currentPocLine := line.new(profileStartBar, pocPrice, profileRight, pocPrice, color=C_ORANGE, width=2)

            if na(currentVahLine)
                currentVahLine := line.new(profileStartBar, vahPrice, profileRight, vahPrice, color=color.new(C_GRAY, 45), style=line.style_dashed)

            if na(currentValLine)
                currentValLine := line.new(profileStartBar, valPrice, profileRight, valPrice, color=color.new(C_GRAY, 45), style=line.style_dashed)

            line.set_xy1(currentPocLine, profileStartBar, pocPrice)
            line.set_xy2(currentPocLine, profileRight, pocPrice)
            line.set_xy1(currentVahLine, profileStartBar, vahPrice)
            line.set_xy2(currentVahLine, profileRight, vahPrice)
            line.set_xy1(currentValLine, profileStartBar, valPrice)
            line.set_xy2(currentValLine, profileRight, valPrice)

            if showProfileTags
                if na(currentPocTag)
                    currentPocTag := label.new(profileRight, pocPrice, "", style=label.style_label_left, color=C_ORANGE, textcolor=color.white, size=size.tiny)

                if na(currentVahTag)
                    currentVahTag := label.new(profileRight, vahPrice, "", style=label.style_label_left, color=C_CARD, textcolor=C_WHITE, size=size.tiny)

                if na(currentValTag)
                    currentValTag := label.new(profileRight, valPrice, "", style=label.style_label_left, color=C_CARD, textcolor=C_WHITE, size=size.tiny)

                label.set_xy(currentPocTag, profileRight, pocPrice)
                label.set_text(currentPocTag, "POC  " + f_price(pocPrice))
                label.set_xy(currentVahTag, profileRight, vahPrice)
                label.set_text(currentVahTag, "VAH  " + f_price(vahPrice))
                label.set_xy(currentValTag, profileRight, valPrice)
                label.set_text(currentValTag, "VAL  " + f_price(valPrice))
            else
                if not na(currentPocTag)
                    label.delete(currentPocTag)
                    currentPocTag := na

                if not na(currentVahTag)
                    label.delete(currentVahTag)
                    currentVahTag := na

                if not na(currentValTag)
                    label.delete(currentValTag)
                    currentValTag := na
    else
        if array.size(currentBuyBoxes) > 0
            for i = 0 to array.size(currentBuyBoxes) - 1
                box.delete(array.get(currentBuyBoxes, i))

        if array.size(currentSellBoxes) > 0
            for i = 0 to array.size(currentSellBoxes) - 1
                box.delete(array.get(currentSellBoxes, i))

        array.clear(currentBuyBoxes)
        array.clear(currentSellBoxes)

        if not na(currentPocLine)
            line.delete(currentPocLine)
            currentPocLine := na

        if not na(currentVahLine)
            line.delete(currentVahLine)
            currentVahLine := na

        if not na(currentValLine)
            line.delete(currentValLine)
            currentValLine := na

        if not na(currentPocTag)
            label.delete(currentPocTag)
            currentPocTag := na

        if not na(currentVahTag)
            label.delete(currentVahTag)
            currentVahTag := na

        if not na(currentValTag)
            label.delete(currentValTag)
            currentValTag := na

// ═══════════════════════════════════════════════════════════════════
// STATE SCORES
// ═══════════════════════════════════════════════════════════════════

int longRequirements = 4 + (useRelativeVolumeConfirm ? 1 : 0) + (usePocMigrationConfirm ? 1 : 0)
int shortRequirements = longRequirements

int longScore =
     (longPocRecent ? 1 : 0) +
     (longSweepRecent ? 1 : 0) +
     (longReclaimRecent ? 1 : 0) +
     (longDeltaStateOk ? 1 : 0) +
     (useRelativeVolumeConfirm and longRelativeVolumeRecent ? 1 : 0) +
     (usePocMigrationConfirm and longPocMigrationOk ? 1 : 0)

int shortScore =
     (shortPocRecent ? 1 : 0) +
     (shortSweepRecent ? 1 : 0) +
     (shortReclaimRecent ? 1 : 0) +
     (shortDeltaStateOk ? 1 : 0) +
     (useRelativeVolumeConfirm and shortRelativeVolumeRecent ? 1 : 0) +
     (usePocMigrationConfirm and shortPocMigrationOk ? 1 : 0)

// ═══════════════════════════════════════════════════════════════════
// STATE BUBBLES
// ═══════════════════════════════════════════════════════════════════

var label longStateBubble = na
var label shortStateBubble = na

if barstate.islast
    if showStateBubbles and longState != STATE_IDLE
        string longStateWord = longState == STATE_READY ? "READY" : "BUILD"
        string longBubbleText = "L " + longStateWord + "  " + str.tostring(longScore) + "/" + str.tostring(longRequirements)

        if na(longStateBubble)
            longStateBubble := label.new(bar_index + 1, low - atr * 0.35, longBubbleText, style=label.style_label_up, color=longState == STATE_READY ? C_GREEN : C_BLUE, textcolor=color.white, size=size.tiny)

        label.set_xy(longStateBubble, bar_index + 1, low - atr * 0.35)
        label.set_text(longStateBubble, longBubbleText)
        label.set_color(longStateBubble, longState == STATE_READY ? C_GREEN : C_BLUE)
    else
        if not na(longStateBubble)
            label.delete(longStateBubble)
            longStateBubble := na

    if showStateBubbles and shortState != STATE_IDLE
        string shortStateWord = shortState == STATE_READY ? "READY" : "BUILD"
        string shortBubbleText = "S " + shortStateWord + "  " + str.tostring(shortScore) + "/" + str.tostring(shortRequirements)

        if na(shortStateBubble)
            shortStateBubble := label.new(bar_index + 1, high + atr * 0.35, shortBubbleText, style=label.style_label_down, color=shortState == STATE_READY ? C_GREEN : C_SELL, textcolor=color.white, size=size.tiny)

        label.set_xy(shortStateBubble, bar_index + 1, high + atr * 0.35)
        label.set_text(shortStateBubble, shortBubbleText)
        label.set_color(shortStateBubble, shortState == STATE_READY ? C_GREEN : C_SELL)
    else
        if not na(shortStateBubble)
            label.delete(shortStateBubble)
            shortStateBubble := na

// ═══════════════════════════════════════════════════════════════════
// ACTIVE TRADE OVERLAY
// ═══════════════════════════════════════════════════════════════════

var line tradeEntryLine = na
var line tradeStopLine = na
var line tradeTp1Line = na
var line tradeTp2Line = na
var box tradeRiskBox = na
var box tradeRewardBox = na
var label tradeEntryTag = na
var label tradeStopTag = na
var label tradeTp1Tag = na
var label tradeTp2Tag = na

float activeStop = strategy.position_size > 0 ? longStop : strategy.position_size < 0 ? shortStop : na

bool activeTradeVisual =
     showTradeOverlay and
     strategy.position_size != 0 and
     not na(entryPrice) and
     not na(entryBar) and
     not na(activeStop) and
     not na(tp1Price) and
     not na(tp2Price)

if activeTradeVisual
    int tradeRight = bar_index + rightProjectionBars
    color tradeColor = strategy.position_size > 0 ? C_BLUE : C_SELL
    bool stopLocksProfit = strategy.position_size > 0 ? activeStop >= entryPrice : activeStop <= entryPrice
    color stopColor = stopLocksProfit ? C_GREEN : C_RED

    if na(tradeEntryLine)
        tradeEntryLine := line.new(entryBar, entryPrice, tradeRight, entryPrice, color=color.new(C_WHITE, 45), style=line.style_dotted)

    line.set_xy1(tradeEntryLine, entryBar, entryPrice)
    line.set_xy2(tradeEntryLine, tradeRight, entryPrice)

    if na(tradeStopLine)
        tradeStopLine := line.new(entryBar, activeStop, tradeRight, activeStop, color=stopColor, width=2)

    line.set_xy1(tradeStopLine, entryBar, activeStop)
    line.set_xy2(tradeStopLine, tradeRight, activeStop)
    line.set_color(tradeStopLine, stopColor)

    if na(tradeTp1Line)
        tradeTp1Line := line.new(entryBar, tp1Price, tradeRight, tp1Price, color=color.new(C_GREEN, 35), style=line.style_dashed)

    line.set_xy1(tradeTp1Line, entryBar, tp1Price)
    line.set_xy2(tradeTp1Line, tradeRight, tp1Price)

    if na(tradeTp2Line)
        tradeTp2Line := line.new(entryBar, tp2Price, tradeRight, tp2Price, color=C_GREEN, width=2)

    line.set_xy1(tradeTp2Line, entryBar, tp2Price)
    line.set_xy2(tradeTp2Line, tradeRight, tp2Price)

    float riskTop = math.max(entryPrice, activeStop)
    float riskBottom = math.min(entryPrice, activeStop)
    float rewardTop = math.max(entryPrice, tp2Price)
    float rewardBottom = math.min(entryPrice, tp2Price)

    if na(tradeRiskBox)
        tradeRiskBox := box.new(entryBar, riskTop, tradeRight, riskBottom, border_color=color.new(stopColor, 100), bgcolor=color.new(stopColor, 92))

    if na(tradeRewardBox)
        tradeRewardBox := box.new(entryBar, rewardTop, tradeRight, rewardBottom, border_color=color.new(tradeColor, 100), bgcolor=color.new(tradeColor, 94))

    box.set_left(tradeRiskBox, entryBar)
    box.set_right(tradeRiskBox, tradeRight)
    box.set_top(tradeRiskBox, riskTop)
    box.set_bottom(tradeRiskBox, riskBottom)
    box.set_bgcolor(tradeRiskBox, color.new(stopColor, 92))

    box.set_left(tradeRewardBox, entryBar)
    box.set_right(tradeRewardBox, tradeRight)
    box.set_top(tradeRewardBox, rewardTop)
    box.set_bottom(tradeRewardBox, rewardBottom)

    if na(tradeEntryTag)
        tradeEntryTag := label.new(tradeRight, entryPrice, "", style=label.style_label_left, color=C_CARD, textcolor=C_WHITE, size=size.tiny)

    if na(tradeStopTag)
        tradeStopTag := label.new(tradeRight, activeStop, "", style=label.style_label_left, color=stopColor, textcolor=color.white, size=size.tiny)

    if na(tradeTp1Tag)
        tradeTp1Tag := label.new(tradeRight, tp1Price, "", style=label.style_label_left, color=color.new(C_GREEN, 15), textcolor=color.white, size=size.tiny)

    if na(tradeTp2Tag)
        tradeTp2Tag := label.new(tradeRight, tp2Price, "", style=label.style_label_left, color=C_GREEN, textcolor=color.white, size=size.tiny)

    label.set_xy(tradeEntryTag, tradeRight, entryPrice)
    label.set_text(tradeEntryTag, "ENTRY  " + f_price(entryPrice))
    label.set_xy(tradeStopTag, tradeRight, activeStop)
    label.set_text(tradeStopTag, (stopLocksProfit ? "LOCK  " : "SL  ") + f_price(activeStop))
    label.set_color(tradeStopTag, stopColor)
    label.set_xy(tradeTp1Tag, tradeRight, tp1Price)
    label.set_text(tradeTp1Tag, "TP1  " + f_price(tp1Price))
    label.set_xy(tradeTp2Tag, tradeRight, tp2Price)
    label.set_text(tradeTp2Tag, "TP2  " + f_price(tp2Price))
else
    if not na(tradeEntryLine)
        line.delete(tradeEntryLine)
        tradeEntryLine := na

    if not na(tradeStopLine)
        line.delete(tradeStopLine)
        tradeStopLine := na

    if not na(tradeTp1Line)
        line.delete(tradeTp1Line)
        tradeTp1Line := na

    if not na(tradeTp2Line)
        line.delete(tradeTp2Line)
        tradeTp2Line := na

    if not na(tradeRiskBox)
        box.delete(tradeRiskBox)
        tradeRiskBox := na

    if not na(tradeRewardBox)
        box.delete(tradeRewardBox)
        tradeRewardBox := na

    if not na(tradeEntryTag)
        label.delete(tradeEntryTag)
        tradeEntryTag := na

    if not na(tradeStopTag)
        label.delete(tradeStopTag)
        tradeStopTag := na

    if not na(tradeTp1Tag)
        label.delete(tradeTp1Tag)
        tradeTp1Tag := na

    if not na(tradeTp2Tag)
        label.delete(tradeTp2Tag)
        tradeTp2Tag := na

// ═══════════════════════════════════════════════════════════════════
// DASHBOARD
// ═══════════════════════════════════════════════════════════════════

resolvedDashboardPosition =
     dashboardPosition == "Top Left" ? position.top_left :
     dashboardPosition == "Bottom Right" ? position.bottom_right :
     dashboardPosition == "Bottom Left" ? position.bottom_left :
     position.top_right

dashboardTextSize =
     dashboardFont == "Large" ? size.normal :
     dashboardFont == "Phone" ? size.small :
     size.tiny

var table dashboard = table.new(resolvedDashboardPosition, 4, 9, bgcolor=color.new(C_NAVY, 8), border_width=0, frame_width=1, frame_color=color.new(C_GRAY, 65))

if barstate.isfirst
    table.merge_cells(dashboard, 0, 0, 3, 0)

f_dashboardCell(int column, int row, string txt, color bg, color fg) =>
    table.cell(dashboard, column, row, txt, bgcolor=bg, text_color=fg, text_size=dashboardTextSize)

f_dashboardPair(int row, string leftLabel, string leftValue, color leftColor, string rightLabel, string rightValue, color rightColor) =>
    f_dashboardCell(0, row, leftLabel, color.new(C_CARD, 8), color.new(C_WHITE, 25))
    f_dashboardCell(1, row, leftValue, color.new(leftColor, 10), C_WHITE)
    f_dashboardCell(2, row, rightLabel, color.new(C_CARD, 8), color.new(C_WHITE, 25))
    f_dashboardCell(3, row, rightValue, color.new(rightColor, 10), C_WHITE)

string positionText = strategy.position_size > 0 ? "LONG" : strategy.position_size < 0 ? "SHORT" : "FLAT"
color positionColor = strategy.position_size > 0 ? C_BLUE : strategy.position_size < 0 ? C_SELL : C_GRAY

string trendText = localBullish ? "BULL" : localBearish ? "BEAR" : "MIXED"
color trendColor = localBullish ? C_BLUE : localBearish ? C_SELL : C_GRAY

string htfText = not useHtfTrendFilter ? "OFF" : htfBullish ? "BULL" : htfBearish ? "BEAR" : "MIXED"
color htfColor = not useHtfTrendFilter ? C_GRAY : htfBullish ? C_BLUE : htfBearish ? C_SELL : C_ORANGE

string setupStateText =
     longState == STATE_READY and shortState == STATE_READY ? "DUAL READY" :
     longState == STATE_READY ? "LONG READY" :
     shortState == STATE_READY ? "SHORT READY" :
     longState == STATE_BUILD and shortState == STATE_BUILD ? "DUAL BUILD" :
     longState == STATE_BUILD ? "LONG BUILD" :
     shortState == STATE_BUILD ? "SHORT BUILD" :
     "IDLE"

color setupStateColor =
     longState == STATE_READY or shortState == STATE_READY ? C_GREEN :
     longState == STATE_BUILD ? C_BLUE :
     shortState == STATE_BUILD ? C_SELL :
     C_GRAY

string activeBlocker =
     longState != STATE_IDLE and shortState == STATE_IDLE ? longBlocker :
     shortState != STATE_IDLE and longState == STATE_IDLE ? shortBlocker :
     localBullish ? longBlocker :
     localBearish ? shortBlocker :
     "NO DIRECTION"

color blockerColor =
     activeBlocker == "CLEAR" ? C_GREEN :
     activeBlocker == "WAIT SETUP" or activeBlocker == "NO DIRECTION" ? C_GRAY :
     activeBlocker == "COOLDOWN" or activeBlocker == "SPACING" ? C_ORANGE :
     C_RED

string deltaText = not deltaReady ? "—" : str.tostring(deltaPct, "#.0") + "%"
color deltaColor = not deltaReady ? C_GRAY : deltaPct > 0 ? C_GREEN : deltaPct < 0 ? C_RED : C_GRAY
string deltaSource = hasLtfDelta ? "LTF " + str.tostring(intrabarCount) : deltaFallbackToBar ? "BAR" : "NONE"

string cooldownText = cooldownPass ? "READY" : str.tostring(cooldownRemaining) + " B"
color cooldownColor = cooldownPass ? C_GREEN : C_ORANGE

string evidenceText =
     shortState != STATE_IDLE and longState == STATE_IDLE
     ? str.tostring(shortScore) + "/" + str.tostring(shortRequirements)
     : str.tostring(longScore) + "/" + str.tostring(longRequirements)

string managementText =
     strategy.position_size == 0 ? "INACTIVE" :
     trailingActive ? "TRAIL" :
     breakEvenActive ? "B/E" :
     tp1Hit ? "TP1 HIT" :
     "ACTIVE"

color managementColor =
     strategy.position_size == 0 ? C_GRAY :
     trailingActive ? C_GREEN :
     breakEvenActive ? C_BLUE :
     C_ORANGE

if barstate.islast
    table.clear(dashboard, 0, 0, 3, 8)

    if showDashboard
        table.cell(dashboard, 0, 0, "RANGER V2  •  " + syminfo.ticker + "  " + timeframe.period, bgcolor=C_NAVY, text_color=C_WHITE, text_size=dashboardTextSize, text_halign=text.align_left)

        f_dashboardPair(1, "POSITION", positionText, positionColor, "TREND", trendText, trendColor)
        f_dashboardPair(2, "STATE", setupStateText, setupStateColor, "BLOCK", activeBlocker, blockerColor)
        f_dashboardPair(3, "HTF", htfText, htfColor, "ADX", str.tostring(adx, "#.0"), adxOk ? C_GREEN : C_RED)
        f_dashboardPair(4, "POC", f_price(pocPrice), C_ORANGE, "EVIDENCE", evidenceText, setupStateColor)
        f_dashboardPair(5, "DELTA", deltaText, deltaColor, "SOURCE", deltaSource, hasLtfDelta ? C_BLUE : C_GRAY)
        f_dashboardPair(6, "RVOL", f_number(relativeVolume), relativeVolumeNow ? C_GREEN : C_GRAY, "COOLDOWN", cooldownText, cooldownColor)
        f_dashboardPair(7, "VAH", f_price(vahPrice), C_CARD, "VAL", f_price(valPrice), C_CARD)
        f_dashboardPair(8, "MANAGE", managementText, managementColor, "SL / TP1", f_price(activeStop) + " / " + f_price(tp1Price), managementColor)

// ═══════════════════════════════════════════════════════════════════
// ALERT CONDITIONS
// ═══════════════════════════════════════════════════════════════════

alertcondition(longEntry, title="Ranger V2 Long Entry", message="BULLISH ENTRY | Ranger V2 | {{ticker}} {{interval}}")
alertcondition(shortEntry, title="Ranger V2 Short Entry", message="BEARISH ENTRY | Ranger V2 | {{ticker}} {{interval}}")
alertcondition(longPositionClosed, title="Ranger V2 Long Exit", message="LONG EXIT | Ranger V2 | {{ticker}} {{interval}}")
alertcondition(shortPositionClosed, title="Ranger V2 Short Exit", message="SHORT EXIT | Ranger V2 | {{ticker}} {{interval}}")

// ═══════════════════════════════════════════════════════════════════
// DATA WINDOW
// ═══════════════════════════════════════════════════════════════════

plot(deltaPct, "LTF Delta %", display=display.data_window)
plot(deltaBuyVolume, "Buy Volume Estimate", display=display.data_window)
plot(deltaSellVolume, "Sell Volume Estimate", display=display.data_window)
plot(relativeVolume, "Relative Volume", display=display.data_window)
plot(pocPrice, "Developing POC", display=display.data_window)
plot(vahPrice, "Developing VAH", display=display.data_window)
plot(valPrice, "Developing VAL", display=display.data_window)
plot(roc, "ROC", display=display.data_window)
plot(bop, "BOP", display=display.data_window)
plot(adx, "ADX", display=display.data_window)

Overview

Trend comes from EMAs, ADX with DI alignment and an optional confirmed higher-timeframe EMA filter, with optional ROC and balance-of-power agreement. A swing level is swept and reclaimed by a candle that meets wick, body and range rules, then the setup is checked against a point-of-control state engine, lower-timeframe volume delta and relative volume. It includes trade frequency limits, session hours, position sizing, a trailing stop and session volume profiles, all drawn on the chart.

Details

Published by

rosijay17-cmd on GitHub

Type

Strategy, Pine Script v6

Draws

On the price chart

Category

SMC / ICT / price action, Volume, Trend

Markets

Any symbol

Timeframes

M15-H1

Risk Notes

  • Signals can change on the live bar or after it closes (repainting)

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Fast EMAint20trendFastLen
Slow EMAint50trendSlowLen
ADX Lengthint14adxLen
Minimum ADXfloat15.0adxMin
Require DI AlignmentbooltruerequireDIAlignment
Use Confirmed HTF TrendbooltrueuseHtfTrendFilter
HTFtimeframe60htfTimeframe
HTF Fast EMAint20htfFastLen
HTF Slow EMAint50htfSlowLen
Require ROC / BOP AgreementboolfalserequireMomentumAgreement
ROC Lengthint14rocLen
Minimum |ROC|float0.05rocMinAbs
Show all 40 settings
SettingTypeDefaultVariable
BOP Smoothingint14bopLen
ATR Lengthint14atrLen
Swing Pivot Lengthint5sweepPivotLength
Stored Liquidity Levelsint20sweepStoredLevels
Minimum Sweep Distance / ATRfloat0.10sweepMinDistanceAtr
Sweep Reclaim RulestringClose Back InsidesweepReclaimRule
Minimum Wick %float0.25sweepMinWickPercent
Maximum Body %float0.75sweepMaxBodyPercent
Minimum Candle Range / ATRfloat0.15sweepMinRangeAtr
Long Reclaim Must Be BullishbooltruesweepRequireBullishBody
Short Reclaim Must Be BearishbooltruesweepRequireBearishBody
Require Sweep Midline ReclaimbooltruesweepRequireMidlineBreak
Setup Memory — Barsint6setupWindowBars
Sweep ↔ POC Distance — ATRfloat0.55pocAnchorDistanceAtr
Setup Invalidation Distance — ATRfloat1.25setupInvalidationAtr
Opposite Sweep Invalidates SetupbooltrueinvalidateOnOppositeSweep
Require Relative VolumebooltrueuseRelativeVolumeConfirm
Relative Volume Averageint20relativeVolLen
Minimum Relative Volumefloat1.10relativeVolMin
Require POC MigrationboolfalseusePocMigrationConfirm
POC Migration Lookbackint3pocMigrationLookback
Delta Lower Timeframetimeframe2deltaLtf
Fallback To Bar EstimatebooltruedeltaFallbackToBar
Minimum Intrabarsint3minDeltaIntrabars
Directional Delta % Thresholdfloat8.0deltaPctThreshold
Directional Sub-Bar Sharefloat0.55directionalSubbarShare
Require Stacked Directional Sub-BarsbooltruerequireStackedSubbars
Minimum Stacked Sub-Barsint2minStackedSubbars
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

12 stars

This strategy plots on the price chart and looks for three-candle fair value gaps and inverse gaps on Bitcoin. It enters long when a wide enough bullish gap appears in an uptrend with RSI above 30 and volume above its average, and short on the opposite bearish

Strategy v6 SMC / ICT / price actionTrend Repaints
Updated 4 months ago · None stated GitHub repository of BTC 4h FVG/iFVG Strategy

The Judas Indicator

by regardhodler
3 stars

The Judas Indicator strategy calculates a multi-factor composite score combining Volume Profile High Volume Nodes, Cumulative Volume Delta (CVD), pivot proximity, volume absorption, order imbalance, and higher-timeframe trend EMAs. It triggers trade entries wh

Strategy v5 TrendVolume
Updated 8 months ago · None stated GitHub repository of The Judas Indicator

Tn Sovereign SMC v6

by raybird
1 stars

Tn Sovereign SMC v6 is a Smart Money Concepts strategy that overlays trade entries and an status HUD table directly on the price chart. It identifies liquidity sweeps by checking if price pierces recent swing highs or lows before closing back within range, fil

Strategy v6 SMC / ICT / price actionTrend
Updated 2 months ago · None stated GitHub repository of Tn Sovereign SMC v6

VCP Probe 08 - Integration

by pineforge-4pass
1 stars

This integration strategy combines seven analytical layers into a single system drawn directly on the main price chart. It calculates pivot highs and lows, Fair Value Gaps (FVG) scaled by ATR, smoothed RSI momentum, volume z-score anomalies, cumulative volume

Strategy v6 TrendVolume
Updated 6 days ago · Apache-2.0 GitHub repository of VCP Probe 08 - Integration

Combines an upgraded WaveTrend oscillator engine with market structure swing pivots, volume profile point of control (POC), and multi-timeframe trend alignment scoring. Trades are triggered by WaveTrend reversal or pullback continuation signals that pass conte

Strategy v6 Oscillators & momentumTrend
Updated 2 days ago · MIT GitHub repository of WaveTrend v3 [WavesUnchained] — Strategy

More strategys

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers