//@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.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Fast EMA
int
20
trendFastLen
Slow EMA
int
50
trendSlowLen
ADX Length
int
14
adxLen
Minimum ADX
float
15.0
adxMin
Require DI Alignment
bool
true
requireDIAlignment
Use Confirmed HTF Trend
bool
true
useHtfTrendFilter
HTF
timeframe
60
htfTimeframe
HTF Fast EMA
int
20
htfFastLen
HTF Slow EMA
int
50
htfSlowLen
Require ROC / BOP Agreement
bool
false
requireMomentumAgreement
ROC Length
int
14
rocLen
Minimum |ROC|
float
0.05
rocMinAbs
Show all 40 settings
Setting
Type
Default
Variable
BOP Smoothing
int
14
bopLen
ATR Length
int
14
atrLen
Swing Pivot Length
int
5
sweepPivotLength
Stored Liquidity Levels
int
20
sweepStoredLevels
Minimum Sweep Distance / ATR
float
0.10
sweepMinDistanceAtr
Sweep Reclaim Rule
string
Close Back Inside
sweepReclaimRule
Minimum Wick %
float
0.25
sweepMinWickPercent
Maximum Body %
float
0.75
sweepMaxBodyPercent
Minimum Candle Range / ATR
float
0.15
sweepMinRangeAtr
Long Reclaim Must Be Bullish
bool
true
sweepRequireBullishBody
Short Reclaim Must Be Bearish
bool
true
sweepRequireBearishBody
Require Sweep Midline Reclaim
bool
true
sweepRequireMidlineBreak
Setup Memory — Bars
int
6
setupWindowBars
Sweep ↔ POC Distance — ATR
float
0.55
pocAnchorDistanceAtr
Setup Invalidation Distance — ATR
float
1.25
setupInvalidationAtr
Opposite Sweep Invalidates Setup
bool
true
invalidateOnOppositeSweep
Require Relative Volume
bool
true
useRelativeVolumeConfirm
Relative Volume Average
int
20
relativeVolLen
Minimum Relative Volume
float
1.10
relativeVolMin
Require POC Migration
bool
false
usePocMigrationConfirm
POC Migration Lookback
int
3
pocMigrationLookback
Delta Lower Timeframe
timeframe
2
deltaLtf
Fallback To Bar Estimate
bool
true
deltaFallbackToBar
Minimum Intrabars
int
3
minDeltaIntrabars
Directional Delta % Threshold
float
8.0
deltaPctThreshold
Directional Sub-Bar Share
float
0.55
directionalSubbarShare
Require Stacked Directional Sub-Bars
bool
true
requireStackedSubbars
Minimum Stacked Sub-Bars
int
2
minStackedSubbars
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.
This strategy combines Smart Money Concepts (BOS and CHoCH) with moving average trend filtering and volume spikes to generate trade signals. It checks for 20 EMA and 50 EMA alignment, along with volume 20% above its 20-period SMA. Long entries require a bullis
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
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
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
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
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