//@version=6 // ───────────────────────────────────────────────────────────────────────────── // OFS Pro: Order Flow Scalper · STRATEGY (for TradingView's Strategy Tester) // Identical signal + plan logic to the indicator. Commission (% per side) and // slippage (ticks) live in Settings → Properties. Position size = equity × risk %. // Delta/CVD are ESTIMATES (lower-timeframe or candle based), not true order flow. // ───────────────────────────────────────────────────────────────────────────── strategy("OFS Pro: Order Flow Scalper [Strategy]", shorttitle = "OFS Pro S", overlay = true, initial_capital = 10000, default_qty_type = strategy.fixed, default_qty_value = 1, commission_type = strategy.commission.percent, commission_value = 0.04, slippage = 1, process_orders_on_close = true, pyramiding = 0, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 1000) // ═══════════════════════════════════════════════════════════════════════════════ // OFS Pro: Order Flow Scalper | shared core (identical in indicator + strategy) // All "order flow" values are ESTIMATES built from OHLCV / lower-timeframe bars. // Pine Script has no access to the order book or a real footprint. // ═══════════════════════════════════════════════════════════════════════════════ // ───────────────────────────────────────────────────────────────────────────── // 0. INPUT GROUPS // ───────────────────────────────────────────────────────────────────────────── const string G_PRE = "00 · Presets" const string G_DEL = "01 · Delta engine" const string G_LEV = "02 · Key levels" const string G_SES = "03 · Sessions" const string G_SWP = "04 · Sweeps & traps" const string G_ABS = "05 · Absorption" const string G_HTF = "06 · HTF confirmation" const string G_SCO = "07 · Scoring" const string G_FIL = "08 · Filters" const string G_RSK = "09 · Entry, stop, targets" const string G_MGT = "10 · Trade management" const string G_SIZ = "11 · Position size" const string G_VIS = "12 · Visuals" const string G_DSH = "13 · Dashboard" const string G_BT = "14 · Backtest" const string G_ALR = "15 · Alerts" // ───────────────────────────────────────────────────────────────────────────── // 1. INPUTS // ───────────────────────────────────────────────────────────────────────────── presetIn = input.string("Custom", "Preset", options = ["Custom", "Scalp 1m-5m", "Intraday 15m", "Conservative", "Aggressive"], group = G_PRE, tooltip = "Any preset other than Custom OVERRIDES: min sweep (ATR), max reclaim bars, absorption z-score, min score, cooldown, signal mode and the delta-flip rule. Pick Custom to use your own values below.") // Delta ltfIn = input.string("Auto", "Intrabar precision", options = ["Auto", "1S", "5S", "15S", "1"], group = G_DEL, tooltip = "Lower timeframe used to classify each intrabar's volume as buying or selling. Auto: 1m on 2m-1h charts, 5m up to 4h, 15m up to 1D. On 1m charts Auto uses the candle estimate. 1S/5S/15S need a TradingView plan with seconds data.") cvdResetIn = input.string("Daily", "CVD reset", options = ["None", "Daily", "Session"], group = G_DEL) volZLen = input.int(50, "Volume z-score length", minval = 5, group = G_DEL) cvdSlopeLen = input.int(10, "CVD slope length", minval = 2, group = G_DEL) divLen = input.int(20, "Divergence lookback (bars)", minval = 3, group = G_DEL, tooltip = "Price makes a new N-bar high/low while estimated CVD does not.") divRecent = input.int(5, "Divergence counts if within (bars)", minval = 1, group = G_DEL) atrLen = input.int(14, "ATR length", minval = 1, group = G_DEL, tooltip = "Every distance input in ATR uses this ATR, so settings auto-scale across symbols and timeframes.") // Levels usePD = input.bool(true, "Previous day H/L", group = G_LEV, inline = "l1") usePW = input.bool(true, "Previous week H/L", group = G_LEV, inline = "l1") usePM = input.bool(true, "Previous month H/L", group = G_LEV, inline = "l2") useSessLv = input.bool(true, "Session H/L", group = G_LEV, inline = "l2") useVwapLv = input.bool(true, "Anchored VWAP ±SD", group = G_LEV, inline = "l3") usePivots = input.bool(true, "Swing pivots", group = G_LEV, inline = "l3") useEq = input.bool(true, "Equal highs / lows", group = G_LEV, inline = "l4") useVP = input.bool(false, "Volume POC/VAH/VAL (approx.)", group = G_LEV, inline = "l4") vwapAnchorIn = input.string("Daily", "VWAP anchor", options = ["Daily", "Weekly", "Monthly"], group = G_LEV) vwapSd1 = input.float(1.0, "VWAP band 1 (SD)", minval = 0.1, step = 0.1, group = G_LEV) vwapSd2 = input.float(2.0, "VWAP band 2 (SD)", minval = 0.1, step = 0.1, group = G_LEV) pivLen = input.int(5, "Pivot strength (bars each side)", minval = 1, group = G_LEV, tooltip = "Pivots are confirmed after this many bars, so they never repaint.") maxPivots = input.int(6, "Pivots remembered (per side)", minval = 1, maxval = 20, group = G_LEV) eqTolAtr = input.float(0.10, "Equal H/L tolerance (ATR)", minval = 0.01, step = 0.01, group = G_LEV) vpLen = input.int(200, "Volume profile lookback", minval = 20, maxval = 500, group = G_LEV) vpRows = input.int(30, "Volume profile rows", minval = 10, maxval = 100, group = G_LEV) vaPct = input.float(70, "Value area %", minval = 50, maxval = 95, group = G_LEV) zoneTolAtr = input.float(0.25, "Merge levels into zones within (ATR)", minval = 0.0, step = 0.05, group = G_LEV) levelRangeAtr= input.float(12, "Only use levels within (ATR) of price", minval = 1, group = G_LEV) // Sessions sessTz = input.string("America/New_York", "Session timezone", group = G_SES, tooltip = "IANA name, e.g. America/New_York, Europe/London, Etc/UTC.") asiaSess = input.session("1900-0400", "Asia", group = G_SES) lonSess = input.session("0300-1200", "London", group = G_SES) nySess = input.session("0800-1700", "New York", group = G_SES) // Sweeps & traps minWickIn = input.float(0.15, "Min sweep beyond level (ATR)", minval = 0.0, step = 0.01, group = G_SWP) maxReclaimIn = input.int(3, "Max bars to reclaim", minval = 1, group = G_SWP) maxConfirm = input.int(3, "Max bars for confirmation after reclaim", minval = 1, group = G_SWP) needFlipIn = input.bool(true, "Require delta flip on confirmation", group = G_SWP, tooltip = "Trapped-trader rule: the confirmation candle's estimated delta must point in the signal direction.") confRefIn = input.string("Reclaim level", "Confirmation must close beyond", options = ["Reclaim level", "Sweep candle midpoint"], group = G_SWP) // Absorption useAbsSig = input.bool(true, "Absorption-reversal signals", group = G_ABS, tooltip = "Also signal when absorption at a zone is followed by a confirmation candle, even without a sweep.") absZIn = input.float(2.0, "Volume z-score ≥", minval = 0.5, step = 0.1, group = G_ABS) absDeltaPct = input.float(25, "|Delta %| of bar volume ≥", minval = 1, maxval = 100, group = G_ABS) absRangeAtr = input.float(0.8, "Bar range ≤ (ATR)", minval = 0.1, step = 0.05, group = G_ABS, tooltip = "Effort without result: big volume, small range.") absClv = input.float(0.5, "Close location ≥ (0-1, favorable side)", minval = 0.0, maxval = 1.0, step = 0.05, group = G_ABS) absTolAtr = input.float(0.25, "Counts as 'at a level' within (ATR)", minval = 0.0, step = 0.05, group = G_ABS) absConfBars = input.int(2, "Bars to confirm absorption", minval = 1, group = G_ABS) // HTF htfModeIn = input.string("Auto", "HTF selection", options = ["Auto", "Manual"], group = G_HTF, tooltip = "Auto: ≤1m→15m, ≤5m→1h, ≤1h→4h, ≤4h→1D, else 1W.") htfManual = input.timeframe("60", "Manual HTF", group = G_HTF) htfTrendType = input.string("EMA stack", "HTF trend method", options = ["EMA stack", "Market structure", "Both must agree"], group = G_HTF) useHtfTrend = input.bool(true, "Filter: HTF trend", group = G_HTF) useHtfVwap = input.bool(true, "Filter: HTF VWAP side", group = G_HTF) useHtfCvd = input.bool(true, "Filter: HTF CVD slope (candle estimate)", group = G_HTF) useHtfKey = input.bool(true, "Filter: don't trade into HTF level", group = G_HTF) htfKeyAtr = input.float(1.0, "…HTF level too close if within (ATR)", minval = 0.1, step = 0.1, group = G_HTF) htfEmaFast = input.int(20, "HTF EMA fast", minval = 2, group = G_HTF) htfEmaSlow = input.int(50, "HTF EMA slow", minval = 3, group = G_HTF) htfStructLen = input.int(3, "HTF structure pivot strength", minval = 1, group = G_HTF) htfRangeLen = input.int(20, "HTF range lookback (bars)", minval = 5, group = G_HTF) htfCvdLen = input.int(5, "HTF CVD slope length", minval = 2, group = G_HTF) modeIn = input.string("Balanced", "Signal mode", options = ["Strict", "Balanced", "Aggressive"], group = G_HTF, tooltip = "Strict: every enabled HTF filter must agree. Balanced: no net HTF opposition. Aggressive: counter-trend allowed only near the HTF range extreme, with a score penalty.") htfExtAtr = input.float(1.5, "Aggressive: HTF extreme within (HTF ATR)", minval = 0.1, step = 0.1, group = G_HTF) aggrPenalty = input.float(0.85, "Aggressive: counter-trend score multiplier", minval = 0.1, maxval = 1.0, step = 0.05, group = G_HTF) // Scoring wLevel = input.float(20, "Weight: level quality", minval = 0, group = G_SCO) wSweep = input.float(20, "Weight: sweep quality", minval = 0, group = G_SCO) wAbs = input.float(15, "Weight: absorption", minval = 0, group = G_SCO) wDiv = input.float(10, "Weight: delta divergence", minval = 0, group = G_SCO) wHtf = input.float(15, "Weight: HTF alignment", minval = 0, group = G_SCO) wSess = input.float(5, "Weight: session quality", minval = 0, group = G_SCO) wVol = input.float(5, "Weight: volatility regime", minval = 0, group = G_SCO) wRR = input.float(10, "Weight: R:R to next liquidity", minval = 0, group = G_SCO) minScoreIn = input.int(60, "Minimum score to show", minval = 0, maxval = 100, group = G_SCO, tooltip = "Grades: A+ ≥ 85, A 70-84, B 55-69, C < 55.") touchLook = input.int(100, "Zone touch lookback (bars)", minval = 1, maxval = 500, group = G_SCO) // Filters tradeAsia = input.bool(true, "Asia", group = G_FIL, inline = "s") tradeLon = input.bool(true, "London", group = G_FIL, inline = "s") tradeNy = input.bool(true, "New York", group = G_FIL, inline = "s") tradeOff = input.bool(false, "Off-hours", group = G_FIL, inline = "s") regimeLen = input.int(100, "Volatility baseline length", minval = 10, group = G_FIL) deadThr = input.float(0.6, "Dead market if ATR/baseline <", minval = 0.0, step = 0.05, group = G_FIL) chaosThr = input.float(2.2, "Chaotic market if ATR/baseline >", minval = 1.0, step = 0.1, group = G_FIL) useRegime = input.bool(true, "Skip dead / chaotic markets", group = G_FIL) minVolMult = input.float(0.5, "Min volume (× average)", minval = 0.0, step = 0.1, group = G_FIL) cooldownIn = input.int(5, "Cooldown bars between signals", minval = 0, group = G_FIL) maxPerSess = input.int(4, "Max signals per session", minval = 1, group = G_FIL) useNoTrade = input.bool(false, "No-trade window", group = G_FIL, inline = "nt") noTradeSess = input.session("0825-0845", "", group = G_FIL, inline = "nt", tooltip = "e.g. around scheduled news. Uses the session timezone.") // Entry / stop / targets entryTypeIn = input.string("Market (confirmation close)", "Entry", options = ["Market (confirmation close)", "Limit (50% of confirmation candle)"], group = G_RSK) limitExpiry = input.int(3, "Limit order expiry (bars)", minval = 1, group = G_RSK) stopBufAtr = input.float(0.10, "Stop buffer beyond extreme (ATR)", minval = 0.0, step = 0.01, group = G_RSK) maxStopAtr = input.float(2.5, "Skip if stop wider than (ATR)", minval = 0.2, step = 0.1, group = G_RSK) tpModeIn = input.string("R multiples", "Targets", options = ["R multiples", "Liquidity (auto)"], group = G_RSK, tooltip = "Liquidity: TP1-3 at the next opposing zones (at least 0.5R away). Missing targets fall back to R multiples.") r1 = input.float(1.0, "TP1 (R)", minval = 0.1, step = 0.1, group = G_RSK, inline = "r") r2 = input.float(2.0, "TP2 (R)", minval = 0.1, step = 0.1, group = G_RSK, inline = "r") r3 = input.float(3.0, "TP3 (R)", minval = 0.1, step = 0.1, group = G_RSK, inline = "r") // Management tp1Pct = input.float(50, "Close % at TP1", minval = 0, maxval = 100, group = G_MGT) tp2Pct = input.float(30, "Close % at TP2", minval = 0, maxval = 100, group = G_MGT, tooltip = "Remainder closes at TP3.") moveBE = input.bool(true, "Move stop to breakeven after TP1", group = G_MGT) trailModeIn = input.string("None", "Trailing stop", options = ["None", "ATR", "Structure"], group = G_MGT) trailAfterTp1= input.bool(true, "Trail only after TP1", group = G_MGT) trailAtr = input.float(1.5, "ATR trail distance", minval = 0.1, step = 0.1, group = G_MGT) trailLen = input.int(5, "Structure trail lookback (bars)", minval = 1, group = G_MGT) useTimeStop = input.bool(true, "Time stop", group = G_MGT, inline = "ts") timeStopBars = input.int(30, "after bars", minval = 1, group = G_MGT, inline = "ts") useInval = input.bool(true, "Exit if signal level is lost (close beyond)", group = G_MGT) // Position size acctSize = input.float(10000, "Account size", minval = 1, group = G_SIZ) riskPct = input.float(0.5, "Risk per trade %", minval = 0.01, step = 0.05, group = G_SIZ) pointValIn = input.float(0, "Value per point per contract (0 = symbol's)", minval = 0, group = G_SIZ, tooltip = "0 uses syminfo.pointvalue (e.g. GC1! = 100, SI1! = 5000, crypto = 1).") maxLev = input.float(10, "Leverage cap (×)", minval = 0.1, step = 0.5, group = G_SIZ) qtyStep = input.float(0.001, "Quantity step", minval = 0.000001, group = G_SIZ) // ───────────────────────────────────────────────────────────────────────────── // 2. PRESETS → effective values // ───────────────────────────────────────────────────────────────────────────── bool isCustom = presetIn == "Custom" float eMinWick = isCustom ? minWickIn : presetIn == "Scalp 1m-5m" ? 0.12 : presetIn == "Intraday 15m" ? 0.15 : presetIn == "Conservative" ? 0.20 : 0.08 int eMaxReclaim= isCustom ? maxReclaimIn : presetIn == "Scalp 1m-5m" ? 3 : presetIn == "Intraday 15m" ? 2 : presetIn == "Conservative" ? 2 : 4 float eAbsZ = isCustom ? absZIn : presetIn == "Scalp 1m-5m" ? 2.0 : presetIn == "Intraday 15m" ? 1.8 : presetIn == "Conservative" ? 2.5 : 1.5 int eMinScore = isCustom ? minScoreIn : presetIn == "Scalp 1m-5m" ? 60 : presetIn == "Intraday 15m" ? 65 : presetIn == "Conservative" ? 75 : 50 int eCooldown = isCustom ? cooldownIn : presetIn == "Scalp 1m-5m" ? 5 : presetIn == "Intraday 15m" ? 3 : presetIn == "Conservative" ? 8 : 2 string eMode = isCustom ? modeIn : presetIn == "Conservative" ? "Strict" : presetIn == "Aggressive" ? "Aggressive" : "Balanced" bool eNeedFlip = isCustom ? needFlipIn : presetIn != "Aggressive" // ───────────────────────────────────────────────────────────────────────────── // 3. TYPES // ───────────────────────────────────────────────────────────────────────────── // A single key level. type Level float price string name int weight // Nearby levels merged into one zone. type Zone float top float bot int count int score string names // Running high/low for a trading session. type SessTrack float hi = na float lo = na float lastHi = na float lastLo = na bool active = false // A liquidity sweep tracked until confirmation, failure or expiry. type Sweep bool active = false bool reclaimed = false bool absorbed = false float level = na float extreme = na float mid = na int startBar = na int reclaimBar = na int zScore = 0 int zCount = 0 string zName = "" float zTop = na float zBot = na float depthAtr = 0.0 float sweepDelta = 0.0 // An absorption bar waiting for a confirmation candle. type AbsSetup bool active = false int startBar = na float hi = na float lo = na float level = na float volZ = 0.0 int zScore = 0 int zCount = 0 string zName = "" float zTop = na float zBot = na // ───────────────────────────────────────────────────────────────────────────── // 4. HELPERS // ───────────────────────────────────────────────────────────────────────────── f_clamp01(float x) => na(x) ? 0.0 : math.max(0.0, math.min(1.0, x)) f_safeDiv(float a, float b, float dflt) => na(a) or na(b) or b == 0 ? dflt : a / b f_grade(float s) => s >= 85 ? "A+" : s >= 70 ? "A" : s >= 55 ? "B" : "C" f_px(float p) => na(p) ? "—" : str.tostring(p, format.mintick) // Intrabar delta: each lower-timeframe bar's volume is signed by its own direction // (close vs open); dojis use the change from the previous intrabar close. f_intrabarDelta(array o, array c, array v) => float d = 0.0 int n = na(c) ? 0 : c.size() float prevC = na if n > 0 for i = 0 to n - 1 float cc = c.get(i) float oo = o.get(i) float vv = nz(v.get(i)) float sgn = cc > oo ? 1.0 : cc < oo ? -1.0 : na(prevC) ? 0.0 : cc > prevC ? 1.0 : cc < prevC ? -1.0 : 0.0 d += sgn * vv prevC := cc [d, n] // Adds a level when enabled, valid and close enough to price to matter. f_addLevel(array arr, float p, string nm, int w, bool on, float ref, float maxDist) => bool ok = on and not na(p) and math.abs(p - ref) <= maxDist if ok arr.push(Level.new(p, nm, w)) ok // Sorts levels by price and merges any within `tol` into zones. f_buildZones(array lv, float tol) => array zs = array.new() int n = lv.size() if n > 0 array px = array.new_float() for l in lv px.push(l.price) array idx = px.sort_indices(order.ascending) Zone cur = na for i in idx Level lvl = lv.get(i) if na(cur) cur := Zone.new(lvl.price, lvl.price, 1, lvl.weight, lvl.name) else if lvl.price - cur.top <= tol cur.top := math.max(cur.top, lvl.price) cur.count := cur.count + 1 cur.score := cur.score + lvl.weight cur.names := cur.names + " · " + lvl.name else zs.push(cur) cur := Zone.new(lvl.price, lvl.price, 1, lvl.weight, lvl.name) if not na(cur) zs.push(cur) zs // Best-scoring zone swept on this bar. dir = 1 sweep of lows (long setup), -1 sweep of highs. f_findSweep(array zs, int dir, float minPen, float prevClose) => int best = -1 int bestScore = -1 int n = zs.size() if n > 0 and not na(prevClose) for i = 0 to n - 1 Zone z = zs.get(i) bool hit = dir == 1 ? low <= z.bot - minPen and prevClose > z.bot : high >= z.top + minPen and prevClose < z.top if hit and z.score > bestScore best := i bestScore := z.score best // Zone the current bar is trading at. dir = 1 support, -1 resistance. f_touchZone(array zs, int dir, float tol) => int best = -1 int bestScore = -1 int n = zs.size() if n > 0 for i = 0 to n - 1 Zone z = zs.get(i) bool at = dir == 1 ? low <= z.top + tol and high >= z.bot - tol and close >= z.bot : high >= z.bot - tol and low <= z.top + tol and close <= z.top if at and z.score > bestScore best := i bestScore := z.score best // Nearest opposing zone edge at least `minDist` beyond `ref` in direction `dir`. f_nearestBeyond(array zs, int dir, float ref, float minDist) => float best = na for z in zs float p = dir == 1 ? z.bot : z.top if dir * (p - ref) >= minDist if na(best) or dir * (p - best) < 0 best := p best // TP1-3: R multiples or the next opposing liquidity zones, always monotonic. f_targets(array zs, int dir, float entry, float rsk, bool liq) => float t1 = entry + dir * r1 * rsk float t2 = entry + dir * r2 * rsk float t3 = entry + dir * r3 * rsk if liq array c = array.new_float() for z in zs float p = dir == 1 ? z.bot : z.top if dir * (p - entry) >= 0.5 * rsk c.push(p) c.sort(dir == 1 ? order.ascending : order.descending) int k = c.size() if k > 0 t1 := c.get(0) if k > 1 t2 := c.get(1) if k > 2 t3 := c.get(2) if dir * (t2 - t1) <= 0 t2 := t1 + dir * math.max(r2 - r1, 0.5) * rsk if dir * (t3 - t2) <= 0 t3 := t2 + dir * math.max(r3 - r2, 0.5) * rsk [t1, t2, t3] // HTF alignment: 0 = fully against, 0.5 = neutral, 1 = fully aligned. f_alignment(int dir, int t, int v, int c) => int n = 0 int agree = 0 int against = 0 if useHtfTrend n += 1 agree += t == dir ? 1 : 0 against += t == -dir ? 1 : 0 if useHtfVwap n += 1 agree += v == dir ? 1 : 0 against += v == -dir ? 1 : 0 if useHtfCvd n += 1 agree += c == dir ? 1 : 0 against += c == -dir ? 1 : 0 float sc = n > 0 ? (agree - against + n) / (2.0 * n) : 0.5 [sc, agree, against, n] // Evaluated INSIDE request.security on the HTF. Every value is shifted [1] and the // call uses lookahead_on: TradingView's documented non-repainting pattern, so only // fully closed HTF bars are ever used, identically on history and in realtime. f_htf(int emaF, int emaS, int structLen, int rangeLen, int cvdLen) => float eF = ta.ema(close, emaF) float eS = ta.ema(close, emaS) int emaTrend = close > eF and eF > eS ? 1 : close < eF and eF < eS ? -1 : 0 float phH = ta.pivothigh(high, structLen, structLen) float plH = ta.pivotlow(low, structLen, structLen) float lastPH = ta.valuewhen(not na(phH), phH, 0) float prevPH = ta.valuewhen(not na(phH), phH, 1) float lastPL = ta.valuewhen(not na(plH), plH, 0) float prevPL = ta.valuewhen(not na(plH), plH, 1) int structTrend = lastPH > prevPH and lastPL > prevPL ? 1 : lastPH < prevPH and lastPL < prevPL ? -1 : 0 bool vwAnchor = timeframe.change(timeframe.isintraday ? "D" : "M") float vw = ta.vwap(hlc3, vwAnchor) int vwapSide = close > vw ? 1 : close < vw ? -1 : 0 float r = high - low float cd = r > 0 ? nz(volume) * ((close - low) - (high - close)) / r : 0.0 float cvdH = ta.cum(cd) int cvdSl = cvdH > cvdH[cvdLen] ? 1 : cvdH < cvdH[cvdLen] ? -1 : 0 float rHi = ta.highest(high, rangeLen) float rLo = ta.lowest(low, rangeLen) float hAtrV = ta.atr(14) [emaTrend[1], structTrend[1], vwapSide[1], cvdSl[1], rHi[1], rLo[1], high[1], low[1], hAtrV[1]] // Sweep lifecycle. step() runs once per confirmed bar AFTER the sweep bar and // returns true when the trapped-trader confirmation candle prints. method resetSweep(Sweep s) => s.active := false s.reclaimed := false s.absorbed := false s.sweepDelta := 0.0 s method step(Sweep s, int dir, float d, bool absHere, int maxReclaim, int maxConf, bool needFlip, bool useMid) => bool fired = false if s.active and bar_index > s.startBar s.extreme := dir == 1 ? math.min(s.extreme, low) : math.max(s.extreme, high) if absHere s.absorbed := true bool inside = dir == 1 ? close > s.level : close < s.level if not s.reclaimed s.sweepDelta := s.sweepDelta + d if inside s.reclaimed := true s.reclaimBar := bar_index else if bar_index - s.startBar >= maxReclaim s.resetSweep() else if not inside // Lost the reclaim: back to pending, still bounded by the reclaim window. s.reclaimed := false s.sweepDelta := s.sweepDelta + d if bar_index - s.startBar >= maxReclaim s.resetSweep() if s.active and s.reclaimed float ref = useMid ? (dir == 1 ? math.max(s.level, s.mid) : math.min(s.level, s.mid)) : s.level bool beyond = dir == 1 ? close > ref : close < ref bool body = dir == 1 ? close > open : close < open bool flip = not needFlip or (dir == 1 ? d > 0 : d < 0) if beyond and body and flip fired := true else if bar_index - s.reclaimBar >= maxConf s.resetSweep() fired method update(SessTrack s, bool inS) => if inS and not s.active s.hi := high s.lo := low s.active := true else if inS s.hi := math.max(s.hi, high) s.lo := math.min(s.lo, low) else if s.active s.lastHi := s.hi s.lastLo := s.lo s.active := false s // ───────────────────────────────────────────────────────────────────────────── // 5. CORE SERIES (computed on every bar, unconditionally) // ───────────────────────────────────────────────────────────────────────────── bool barConf = barstate.isconfirmed int tfSec = timeframe.in_seconds() float atrRaw = ta.atr(atrLen) float atrS = math.max(nz(atrRaw, high - low), syminfo.mintick) float rng = high - low float clv = rng > 0 ? (close - low) / rng : 0.5 bool newDay = timeframe.change("D") bool newWeek = timeframe.change("W") bool newMonth= timeframe.change("M") float lowN = ta.lowest(low, trailLen) float highN = ta.highest(high, trailLen) // ───────────────────────────────────────────────────────────────────────────── // 6. SESSIONS // ───────────────────────────────────────────────────────────────────────────── bool inAsia = not na(time(timeframe.period, asiaSess + ":1234567", sessTz)) bool inLon = not na(time(timeframe.period, lonSess + ":1234567", sessTz)) bool inNy = not na(time(timeframe.period, nySess + ":1234567", sessTz)) bool inNoTr = useNoTrade and not na(time(timeframe.period, noTradeSess + ":1234567", sessTz)) string sessName = inLon and inNy ? "London/NY overlap" : inNy ? "New York" : inLon ? "London" : inAsia ? "Asia" : "Off-hours" int sessId = inNy ? 2 : inLon ? 1 : inAsia ? 0 : 3 // stats bucket (overlap counts as NY) bool sessChange= sessName != sessName[1] float sessQ = inLon and inNy ? 1.0 : inNy or inLon ? 0.8 : inAsia ? 0.5 : 0.25 bool sessOk = (inAsia and tradeAsia) or (inLon and tradeLon) or (inNy and tradeNy) or (not inAsia and not inLon and not inNy and tradeOff) var SessTrack trAsia = SessTrack.new() var SessTrack trLon = SessTrack.new() var SessTrack trNy = SessTrack.new() if barConf trAsia.update(inAsia) trLon.update(inLon) trNy.update(inNy) // ───────────────────────────────────────────────────────────────────────────── // 7. DELTA ENGINE (estimated) // ───────────────────────────────────────────────────────────────────────────── string ltfAuto = tfSec <= 60 ? "" : tfSec <= 3600 ? "1" : tfSec <= 14400 ? "5" : tfSec <= 86400 ? "15" : "60" string ltfStr = ltfIn == "Auto" ? ltfAuto : ltfIn string ltfReq = ltfStr == "" ? "1" : ltfStr bool useLtf = ltfStr != "" and timeframe.in_seconds(ltfReq) < tfSec [ltfO, ltfC, ltfV] = request.security_lower_tf(syminfo.tickerid, ltfReq, [open, close, volume], ignore_invalid_timeframe = true) [ibDelta, ibCount] = f_intrabarDelta(ltfO, ltfC, ltfV) float candleDelta = rng > 0 ? nz(volume) * ((close - low) - (high - close)) / rng : 0.0 bool ltfOk = useLtf and ibCount > 1 float delta = ltfOk ? ibDelta : candleDelta string deltaSrc = ltfOk ? "Intrabar " + ltfStr + (str.contains(ltfStr, "S") ? "" : "m") : "Candle estimate" var float cumVol = 0.0 cumVol += nz(volume) bool noVolData = cumVol == 0.0 float deltaPct = nz(volume) > 0 ? 100.0 * delta / volume : 0.0 float vMean = ta.sma(nz(volume), volZLen) float vStd = ta.stdev(nz(volume), volZLen) float volZ = vStd > 0 ? (nz(volume) - vMean) / vStd : 0.0 bool cvdResetNow = cvdResetIn == "Daily" ? newDay : cvdResetIn == "Session" ? sessChange : false var float cvd = 0.0 if cvdResetNow cvd := 0.0 cvd += delta float cvdRaw = ta.cum(delta) // never reset: used for slope/divergence float cvdPast = cvdRaw[cvdSlopeLen] float cvdSlope = na(cvdPast) ? 0.0 : (cvdRaw - cvdPast) / cvdSlopeLen int cvdDir = cvdSlope > 0 ? 1 : cvdSlope < 0 ? -1 : 0 float loPrev = ta.lowest(low, divLen)[1] float hiPrev = ta.highest(high, divLen)[1] float cvdLoPrev = ta.lowest(cvdRaw, divLen)[1] float cvdHiPrev = ta.highest(cvdRaw, divLen)[1] bool bullDiv = low < loPrev and cvdRaw > cvdLoPrev bool bearDiv = high > hiPrev and cvdRaw < cvdHiPrev int bsBullDiv = nz(ta.barssince(bullDiv), 9999) int bsBearDiv = nz(ta.barssince(bearDiv), 9999) // ───────────────────────────────────────────────────────────────────────────── // 8. KEY LEVELS // ───────────────────────────────────────────────────────────────────────────── [pdh, pdl] = request.security(syminfo.tickerid, "D", [high[1], low[1]], lookahead = barmerge.lookahead_on) [pwh, pwl] = request.security(syminfo.tickerid, "W", [high[1], low[1]], lookahead = barmerge.lookahead_on) [pmh, pml] = request.security(syminfo.tickerid, "M", [high[1], low[1]], lookahead = barmerge.lookahead_on) bool vwapReset = vwapAnchorIn == "Weekly" ? newWeek : vwapAnchorIn == "Monthly" ? newMonth : newDay [vwapV, vwU1, vwL1] = ta.vwap(hlc3, vwapReset, vwapSd1) [vwapV2, vwU2, vwL2] = ta.vwap(hlc3, vwapReset, vwapSd2) // Swing pivots + equal highs/lows (resting liquidity pools) float ph = ta.pivothigh(high, pivLen, pivLen) float pl = ta.pivotlow(low, pivLen, pivLen) var array pivH = array.new_float() var array pivL = array.new_float() var float eqhLevel = na var float eqlLevel = na if barConf if not na(ph) for p in pivH if math.abs(p - ph) <= eqTolAtr * atrS eqhLevel := math.max(p, ph) pivH.unshift(ph) if pivH.size() > maxPivots pivH.pop() if not na(pl) for p in pivL if math.abs(p - pl) <= eqTolAtr * atrS eqlLevel := math.min(p, pl) pivL.unshift(pl) if pivL.size() > maxPivots pivL.pop() // An equal high/low that price has accepted beyond (close past it) is spent. if not na(eqhLevel) and close > eqhLevel + eqTolAtr * atrS eqhLevel := na if not na(eqlLevel) and close < eqlLevel - eqTolAtr * atrS eqlLevel := na // Volume profile approximation (each bar's volume binned at its HLC3), every 10 bars. var float vpPoc = na var float vpVah = na var float vpVal = na float vpHi = ta.highest(high, vpLen) float vpLo = ta.lowest(low, vpLen) if useVP and barConf and bar_index >= vpLen and bar_index % 10 == 0 float rowH = (vpHi - vpLo) / vpRows if rowH > 0 array bins = array.new_float(vpRows, 0.0) for i = 0 to vpLen - 1 int b = math.min(vpRows - 1, math.max(0, int(math.floor((hlc3[i] - vpLo) / rowH)))) bins.set(b, bins.get(b) + nz(volume[i])) float total = bins.sum() if total > 0 int pocI = bins.indexof(bins.max()) float acc = bins.get(pocI) int up = pocI int dn = pocI while acc < total * vaPct / 100.0 and (up < vpRows - 1 or dn > 0) float vUp = up < vpRows - 1 ? bins.get(up + 1) : -1.0 float vDn = dn > 0 ? bins.get(dn - 1) : -1.0 if vUp >= vDn up += 1 acc += vUp else dn -= 1 acc += vDn vpPoc := vpLo + (pocI + 0.5) * rowH vpVah := vpLo + (up + 1) * rowH vpVal := vpLo + dn * rowH // Assemble this bar's level set and merge into ranked zones. var array levels = array.new() levels.clear() float maxDist = levelRangeAtr * atrS f_addLevel(levels, pmh, "PMH", 5, usePM, close, maxDist) f_addLevel(levels, pml, "PML", 5, usePM, close, maxDist) f_addLevel(levels, pwh, "PWH", 4, usePW, close, maxDist) f_addLevel(levels, pwl, "PWL", 4, usePW, close, maxDist) f_addLevel(levels, pdh, "PDH", 3, usePD, close, maxDist) f_addLevel(levels, pdl, "PDL", 3, usePD, close, maxDist) f_addLevel(levels, eqhLevel, "EQH", 3, useEq, close, maxDist) f_addLevel(levels, eqlLevel, "EQL", 3, useEq, close, maxDist) f_addLevel(levels, trAsia.lastHi, "Asia H", 2, useSessLv, close, maxDist) f_addLevel(levels, trAsia.lastLo, "Asia L", 2, useSessLv, close, maxDist) f_addLevel(levels, trLon.lastHi, "LDN H", 2, useSessLv, close, maxDist) f_addLevel(levels, trLon.lastLo, "LDN L", 2, useSessLv, close, maxDist) f_addLevel(levels, trNy.lastHi, "NY H", 2, useSessLv, close, maxDist) f_addLevel(levels, trNy.lastLo, "NY L", 2, useSessLv, close, maxDist) f_addLevel(levels, vwapV, "VWAP", 2, useVwapLv, close, maxDist) f_addLevel(levels, vwU1, "VWAP+1σ", 1, useVwapLv, close, maxDist) f_addLevel(levels, vwL1, "VWAP-1σ", 1, useVwapLv, close, maxDist) f_addLevel(levels, vwU2, "VWAP+2σ", 2, useVwapLv, close, maxDist) f_addLevel(levels, vwL2, "VWAP-2σ", 2, useVwapLv, close, maxDist) f_addLevel(levels, vpPoc, "POC", 2, useVP, close, maxDist) f_addLevel(levels, vpVah, "VAH", 1, useVP, close, maxDist) f_addLevel(levels, vpVal, "VAL", 1, useVP, close, maxDist) if usePivots for p in pivH f_addLevel(levels, p, "Swing H", 1, true, close, maxDist) for p in pivL f_addLevel(levels, p, "Swing L", 1, true, close, maxDist) array zones = f_buildZones(levels, zoneTolAtr * atrS) // ───────────────────────────────────────────────────────────────────────────── // 9. HTF CONTEXT (non-repainting) // ───────────────────────────────────────────────────────────────────────────── string htfAuto = tfSec <= 60 ? "15" : tfSec <= 300 ? "60" : tfSec <= 3600 ? "240" : tfSec <= 14400 ? "D" : "W" string htf = htfModeIn == "Auto" ? htfAuto : htfManual bool htfValid = timeframe.in_seconds(htf) > tfSec [hEma, hStruct, hVwap, hCvd, hRangeHi, hRangeLo, hPrevHi, hPrevLo, hAtr] = request.security(syminfo.tickerid, htf, f_htf(htfEmaFast, htfEmaSlow, htfStructLen, htfRangeLen, htfCvdLen), lookahead = barmerge.lookahead_on) int htfTrend = htfTrendType == "EMA stack" ? hEma : htfTrendType == "Market structure" ? hStruct : (hEma == hStruct ? hEma : 0) // ───────────────────────────────────────────────────────────────────────────── // 10. VOLATILITY REGIME + VOLUME // ───────────────────────────────────────────────────────────────────────────── float atrBase = ta.sma(atrS, regimeLen) float atrRatio = f_safeDiv(atrS, atrBase, 1.0) string volRegime = atrRatio < deadThr ? "Dead" : atrRatio > chaosThr ? "Chaotic" : "Normal" float volQ = volRegime == "Normal" ? 1.0 : volRegime == "Chaotic" ? 0.5 : 0.3 bool regimeOk = not useRegime or volRegime == "Normal" bool volOk = noVolData or nz(volume) >= minVolMult * nz(vMean) // ───────────────────────────────────────────────────────────────────────────── // 11. ABSORPTION // ───────────────────────────────────────────────────────────────────────────── var Sweep swL = Sweep.new() // sweep of lows → long setups var Sweep swS = Sweep.new() // sweep of highs → short setups bool absBase = not noVolData and volZ >= eAbsZ and math.abs(deltaPct) >= absDeltaPct and rng <= absRangeAtr * atrS bool absBullRaw = absBase and delta < 0 and clv >= absClv // heavy selling, price held bool absBearRaw = absBase and delta > 0 and clv <= 1.0 - absClv // heavy buying, price held int absZoneL = f_touchZone(zones, 1, absTolAtr * atrS) int absZoneS = f_touchZone(zones, -1, absTolAtr * atrS) bool absBull = absBullRaw and (absZoneL >= 0 or swL.active) bool absBear = absBearRaw and (absZoneS >= 0 or swS.active) bool absBullC = absBull and barConf bool absBearC = absBear and barConf var AbsSetup abL = AbsSetup.new() var AbsSetup abS = AbsSetup.new() bool absSigL = false bool absSigS = false if barConf and useAbsSig // confirm, fail or expire existing absorption setups if abL.active and bar_index > abL.startBar if close < abL.lo abL.active := false else if close > abL.hi and delta > 0 absSigL := true else if bar_index - abL.startBar >= absConfBars abL.active := false if abS.active and bar_index > abS.startBar if close > abS.hi abS.active := false else if close < abS.lo and delta < 0 absSigS := true else if bar_index - abS.startBar >= absConfBars abS.active := false // arm new setups at a zone if absBull and absZoneL >= 0 and not abL.active Zone z = zones.get(absZoneL) abL.active := true abL.startBar := bar_index abL.hi := high abL.lo := low abL.level := z.bot abL.volZ := volZ abL.zScore := z.score abL.zCount := z.count abL.zName := z.names abL.zTop := z.top abL.zBot := z.bot if absBear and absZoneS >= 0 and not abS.active Zone z = zones.get(absZoneS) abS.active := true abS.startBar := bar_index abS.hi := high abS.lo := low abS.level := z.top abS.volZ := volZ abS.zScore := z.score abS.zCount := z.count abS.zName := z.names abS.zTop := z.top abS.zBot := z.bot // ───────────────────────────────────────────────────────────────────────────── // 12. SWEEPS + TRAPPED TRADERS // ───────────────────────────────────────────────────────────────────────────── bool useMid = confRefIn == "Sweep candle midpoint" bool trapL = false bool trapS = false if barConf trapL := swL.step(1, delta, absBull, eMaxReclaim, maxConfirm, eNeedFlip, useMid) trapS := swS.step(-1, delta, absBear, eMaxReclaim, maxConfirm, eNeedFlip, useMid) int swIdxL = f_findSweep(zones, 1, eMinWick * atrS, close[1]) int swIdxS = f_findSweep(zones, -1, eMinWick * atrS, close[1]) bool newBullSweep = false bool newBearSweep = false if barConf and swIdxL >= 0 and not swL.active and not trapL Zone z = zones.get(swIdxL) swL.active := true swL.reclaimed := close > z.bot swL.reclaimBar := close > z.bot ? bar_index : int(na) swL.absorbed := absBullRaw swL.level := z.bot swL.extreme := low swL.mid := (high + low) / 2 swL.startBar := bar_index swL.zScore := z.score swL.zCount := z.count swL.zName := z.names swL.zTop := z.top swL.zBot := z.bot swL.depthAtr := (z.bot - low) / atrS swL.sweepDelta := delta newBullSweep := true if barConf and swIdxS >= 0 and not swS.active and not trapS Zone z = zones.get(swIdxS) swS.active := true swS.reclaimed := close < z.top swS.reclaimBar := close < z.top ? bar_index : int(na) swS.absorbed := absBearRaw swS.level := z.top swS.extreme := high swS.mid := (high + low) / 2 swS.startBar := bar_index swS.zScore := z.score swS.zCount := z.count swS.zName := z.names swS.zTop := z.top swS.zBot := z.bot swS.depthAtr := (high - z.top) / atrS swS.sweepDelta := delta newBearSweep := true // ───────────────────────────────────────────────────────────────────────────── // 13. CANDIDATE SETUP (trap preferred over absorption-only; long checked first) // ───────────────────────────────────────────────────────────────────────────── int cDir = 0 string cType = "" float cLevel = na float cExtreme = na int cZScore = 0 int cZCount = 0 string cZName = "" float cZTop = na float cZBot = na float cDepth = 0.0 bool cAbs = false float cAbsZ = 0.0 int cReclaimBars = 0 float cSweepDelta = 0.0 if trapL cDir := 1 cType := "Sweep + trap" cLevel := swL.level cExtreme := swL.extreme cZScore := swL.zScore cZCount := swL.zCount cZName := swL.zName cZTop := swL.zTop cZBot := swL.zBot cDepth := swL.depthAtr cAbs := swL.absorbed cReclaimBars := nz(swL.reclaimBar, bar_index) - swL.startBar cSweepDelta := swL.sweepDelta else if absSigL cDir := 1 cType := "Absorption" cLevel := abL.level cExtreme := abL.lo cZScore := abL.zScore cZCount := abL.zCount cZName := abL.zName cZTop := abL.zTop cZBot := abL.zBot cAbs := true cAbsZ := abL.volZ else if trapS cDir := -1 cType := "Sweep + trap" cLevel := swS.level cExtreme := swS.extreme cZScore := swS.zScore cZCount := swS.zCount cZName := swS.zName cZTop := swS.zTop cZBot := swS.zBot cDepth := swS.depthAtr cAbs := swS.absorbed cReclaimBars := nz(swS.reclaimBar, bar_index) - swS.startBar cSweepDelta := swS.sweepDelta else if absSigS cDir := -1 cType := "Absorption" cLevel := abS.level cExtreme := abS.hi cZScore := abS.zScore cZCount := abS.zCount cZName := abS.zName cZTop := abS.zTop cZBot := abS.zBot cAbs := true cAbsZ := abS.volZ // Every trigger seen this bar is consumed, so none can fire twice. if trapL swL.resetSweep() if trapS swS.resetSweep() if absSigL abL.active := false if absSigS abS.active := false // Zone touches over the lookback (only computed when a candidate exists). int cTouches = 0 if cDir != 0 float tTol = absTolAtr * atrS for i = 1 to touchLook if low[i] <= cZTop + tTol and high[i] >= cZBot - tTol cTouches += 1 // ───────────────────────────────────────────────────────────────────────────── // 14. TRADE PLAN, SCORE, FILTERS // ───────────────────────────────────────────────────────────────────────────── bool isLimit = entryTypeIn == "Limit (50% of confirmation candle)" float pEntry = na float pStop = na float pRisk = na float pT1 = na float pT2 = na float pT3 = na float pScore = 0.0 string pGrade = "" string pWhyNot = "" string pBreak = "" float pQty = 0.0 float pRiskCash = 0.0 float pRRLiq = na bool sigLong = false bool sigShort = false var int lastSigBar = -100000 var int sessSigCount = 0 if barConf and sessChange sessSigCount := 0 if cDir != 0 float entryRaw = isLimit ? (high + low) / 2 : close pEntry := math.round_to_mintick(entryRaw) float stopBase = cDir == 1 ? math.min(cExtreme, low) : math.max(cExtreme, high) pStop := math.round_to_mintick(stopBase - cDir * stopBufAtr * atrS) pRisk := math.abs(pEntry - pStop) bool riskOk = pRisk >= syminfo.mintick and pRisk <= maxStopAtr * atrS if riskOk [t1, t2, t3] = f_targets(zones, cDir, pEntry, pRisk, tpModeIn == "Liquidity (auto)") pT1 := math.round_to_mintick(t1) pT2 := math.round_to_mintick(t2) pT3 := math.round_to_mintick(t3) float liq = f_nearestBeyond(zones, cDir, pEntry, 0.25 * pRisk) pRRLiq := na(liq) ? r3 : math.abs(liq - pEntry) / pRisk // factor scores (each 0-1) float conf = f_clamp01((cZScore + cZCount) / 10.0) float touchQ = f_clamp01(cTouches / 5.0) float qLevel = f_clamp01(0.7 * conf + 0.3 * touchQ) bool trapped = cDir == 1 ? cSweepDelta < 0 : cSweepDelta > 0 float qSweep = cType == "Sweep + trap" ? f_clamp01(0.6 * f_clamp01(cDepth / math.max(2 * eMinWick, 0.05)) + 0.4 * (1.0 - cReclaimBars / (eMaxReclaim + 1.0)) + (trapped ? 0.2 : 0.0)) : 0.0 float qAbs = cType == "Absorption" ? f_clamp01(cAbsZ / (eAbsZ * 1.5)) : cAbs ? 1.0 : f_clamp01(volZ / (2 * eAbsZ)) * 0.5 float qDiv = (cDir == 1 ? bsBullDiv : bsBearDiv) <= divRecent ? 1.0 : 0.0 [alSc, alAgree, alAgainst, alN] = f_alignment(cDir, htfTrend, hVwap, hCvd) bool counter = alSc < 0.5 bool nearExt = cDir == 1 ? (pEntry - hRangeLo) <= htfExtAtr * nz(hAtr, atrS) : (hRangeHi - pEntry) <= htfExtAtr * nz(hAtr, atrS) float qHtf = counter and eMode == "Aggressive" ? alSc * 0.5 : alSc float qRR = f_clamp01(nz(pRRLiq) / 3.0) float wSum = wLevel + wSweep + wAbs + wDiv + wHtf + wSess + wVol + wRR float rawSc = wSum > 0 ? 100.0 * (wLevel * qLevel + wSweep * qSweep + wAbs * qAbs + wDiv * qDiv + wHtf * qHtf + wSess * sessQ + wVol * volQ + wRR * qRR) / wSum : 0.0 pScore := math.round(counter and eMode == "Aggressive" ? rawSc * aggrPenalty : rawSc) pGrade := f_grade(pScore) pBreak := "Level " + str.tostring(qLevel * 100, "#") + " · Sweep " + str.tostring(qSweep * 100, "#") + " · Abs " + str.tostring(qAbs * 100, "#") + " · Div " + str.tostring(qDiv * 100, "#") + " · HTF " + str.tostring(qHtf * 100, "#") + " · Sess " + str.tostring(sessQ * 100, "#") + " · Vol " + str.tostring(volQ * 100, "#") + " · R:R " + str.tostring(qRR * 100, "#") // HTF gate bool htfPass = eMode == "Strict" ? (alN == 0 or alAgree == alN) : eMode == "Balanced" ? alSc >= 0.5 : (not counter or nearExt) bool keyBlock = false if useHtfKey float d1 = cDir == 1 ? hPrevHi - pEntry : pEntry - hPrevLo float d2 = cDir == 1 ? hRangeHi - pEntry : pEntry - hRangeLo keyBlock := (d1 > 0 and d1 < htfKeyAtr * atrS) or (d2 > 0 and d2 < htfKeyAtr * atrS) // position size float pv = pointValIn > 0 ? pointValIn : syminfo.pointvalue float riskCashTarget = acctSize * riskPct / 100.0 float qRaw = pRisk > 0 and pv > 0 ? riskCashTarget / (pRisk * pv) : 0.0 float qCap = pEntry > 0 and pv > 0 ? acctSize * maxLev / (pEntry * pv) : qRaw pQty := math.floor(math.min(qRaw, qCap) / qtyStep) * qtyStep pRiskCash := pQty * pRisk * pv // first reason a candidate is rejected (empty = signal) pWhyNot := not riskOk ? "stop too wide or too tight" : not htfPass ? "HTF disagrees (" + eMode + ")" : keyBlock ? "HTF level in the way" : not sessOk ? "session filtered" : inNoTr ? "no-trade window" : not regimeOk ? volRegime + " market" : not volOk ? "low volume" : bar_index - lastSigBar <= eCooldown ? "cooldown" : sessSigCount >= maxPerSess ? "session signal cap" : pScore < eMinScore ? "score " + str.tostring(pScore, "#") + " < " + str.tostring(eMinScore) : "" if pWhyNot == "" and barConf sigLong := cDir == 1 sigShort := cDir == -1 lastSigBar := bar_index sessSigCount += 1 bool sigAny = sigLong or sigShort int sigDir = sigLong ? 1 : sigShort ? -1 : 0 // Last rejected candidate (dashboard context) var string lastReject = "" if barConf and cDir != 0 and not sigAny lastReject := (cDir == 1 ? "Long " : "Short ") + cType + " skipped: " + pWhyNot // ═══════════════════════════════════════════════════════════════════════════════ // 15. STRATEGY ORDER LOGIC (strategy only) // Same signals and plan as the indicator. Commission and slippage are set in the // strategy() declaration (Properties tab). Note: TradingView's broker emulator // decides the intrabar order of SL vs TP from the OHLC path (or Bar Magnifier), // not the indicator's "stop first" rule, so results will differ slightly. // ═══════════════════════════════════════════════════════════════════════════════ var int sDir = 0 var int sSigBar = na var int sEntryBar = na var float sEntry = na var float sStop = na var float sT1 = na var float sT2 = na var float sT3 = na var float sLevel = na var float sQ1 = 0.0 var float sQ2 = 0.0 var bool sHit1 = false var bool sHit2 = false float pvS = pointValIn > 0 ? pointValIn : syminfo.pointvalue float posAbs = math.abs(strategy.position_size) // position closed (by an exit) → reset state if sDir != 0 and not na(sEntryBar) and posAbs == 0 sDir := 0 sEntryBar := na // record the fill if sDir != 0 and na(sEntryBar) and posAbs > 0 sEntryBar := bar_index sEntry := strategy.position_avg_price // unfilled limit expiry if sDir != 0 and na(sEntryBar) and bar_index - sSigBar > limitExpiry strategy.cancel(sDir == 1 ? "L" : "S") sDir := 0 // new signal (flat and no pending order) if barConf and sigAny and sDir == 0 and posAbs == 0 float riskCash = strategy.equity * riskPct / 100.0 float qRawS = pRisk > 0 and pvS > 0 ? riskCash / (pRisk * pvS) : 0.0 float qCapS = pEntry > 0 and pvS > 0 ? strategy.equity * maxLev / (pEntry * pvS) : qRawS float q = math.floor(math.min(qRawS, qCapS) / qtyStep) * qtyStep if q > 0 sDir := sigDir sSigBar := bar_index sEntryBar := na sEntry := pEntry sStop := pStop sT1 := pT1 sT2 := pT2 sT3 := pT3 sLevel := cLevel sQ1 := math.floor(q * math.min(tp1Pct, 100.0) / 100.0 / qtyStep) * qtyStep sQ2 := math.floor(q * math.max(0.0, math.min(tp2Pct, 100.0 - tp1Pct)) / 100.0 / qtyStep) * qtyStep sHit1 := false sHit2 := false strategy.entry(sDir == 1 ? "L" : "S", sDir == 1 ? strategy.long : strategy.short, qty = q, limit = isLimit ? pEntry : na, comment = pGrade + " " + str.tostring(pScore, "#")) string sId = sDir == 1 ? "L" : "S" // manage the open position if sDir != 0 and not na(sEntryBar) and posAbs > 0 and bar_index > sEntryBar if not sHit1 and (sDir == 1 ? high >= sT1 : low <= sT1) sHit1 := true if moveBE sStop := sDir == 1 ? math.max(sStop, sEntry) : math.min(sStop, sEntry) if sHit1 and not sHit2 and (sDir == 1 ? high >= sT2 : low <= sT2) sHit2 := true bool invalidS = useInval and (sDir == 1 ? close < sLevel : close > sLevel) bool timeoutS = useTimeStop and bar_index - sEntryBar >= timeStopBars if invalidS or timeoutS strategy.close(sId, comment = invalidS ? "Invalidated" : "Time stop") else if trailModeIn != "None" and (not trailAfterTp1 or sHit1) float nsS = trailModeIn == "ATR" ? close - sDir * trailAtr * atrS : (sDir == 1 ? lowN : highN) if sDir * (nsS - sStop) > 0 sStop := math.round_to_mintick(nsS) // (re)issue the bracket every bar so stop moves take effect if sDir != 0 if not sHit1 and sQ1 > 0 strategy.exit("TP1", sId, qty = sQ1, limit = sT1, stop = sStop, comment_profit = "TP1", comment_loss = "SL") if not sHit2 and sQ2 > 0 strategy.exit("TP2", sId, qty = sQ2, limit = sT2, stop = sStop, comment_profit = "TP2", comment_loss = "SL") strategy.exit("TP3", sId, limit = sT3, stop = sStop, comment_profit = "TP3", comment_loss = "SL/BE") // Trade state exposed to the shared dashboard / visuals. int posDir = strategy.position_size > 0 ? 1 : strategy.position_size < 0 ? -1 : 0 bool posPending = sDir != 0 and posAbs == 0 float posEntry = sDir != 0 ? sEntry : na float posStop = sDir != 0 ? sStop : na float posTp1 = sDir != 0 and not sHit1 ? sT1 : na float posTp2 = sDir != 0 and not sHit2 ? sT2 : na float posTp3 = sDir != 0 ? sT3 : na int posPendDir = posPending ? sDir : 0 // ═══════════════════════════════════════════════════════════════════════════════ // 16. VISUALS (shared). Everything price-based is drawn on the main chart with // force_overlay = true. Object counts are capped and old objects deleted. // ═══════════════════════════════════════════════════════════════════════════════ themeIn = input.string("Dark", "Theme", options = ["Dark", "Light", "Custom"], group = G_VIS) cBullIn = input.color(#26A69A, "Custom bull", group = G_VIS, inline = "c1") cBearIn = input.color(#EF5350, "bear", group = G_VIS, inline = "c1") cZoneIn = input.color(#787B86, "zone", group = G_VIS, inline = "c2") cAccentIn = input.color(#F7C548, "accent", group = G_VIS, inline = "c2") cTextIn = input.color(#D1D4DC, "text", group = G_VIS, inline = "c3") cPanelIn = input.color(#131722, "panel", group = G_VIS, inline = "c3") showZones = input.bool(true, "Show zones", group = G_VIS, inline = "v1") showLvLines = input.bool(false, "Show individual level lines", group = G_VIS, inline = "v1") maxZonesDraw = input.int(10, "Max zones drawn (nearest first)", minval = 1, maxval = 40, group = G_VIS) zoneLookback = input.int(60, "Zone left extension (bars)", minval = 5, maxval = 400, group = G_VIS) zoneMinW = input.float(0.08, "Min zone height (ATR)", minval = 0.0, step = 0.01, group = G_VIS) showVwapPlot = input.bool(true, "Plot anchored VWAP + bands", group = G_VIS) showSweeps = input.bool(true, "Sweep markers", group = G_VIS, inline = "v2") showAbs = input.bool(true, "Absorption markers", group = G_VIS, inline = "v2") showSigLbl = input.bool(true, "Signal labels", group = G_VIS, inline = "v3") lblDetail = input.string("Full plan", "", options = ["Full plan", "Compact"], group = G_VIS, inline = "v3") lblSizeIn = input.string("Small", "Label size", options = ["Tiny", "Small", "Normal"], group = G_VIS) maxSigLbl = input.int(100, "Max signal labels kept", minval = 10, maxval = 250, group = G_VIS) showTrLines = input.bool(true, "Active trade lines (entry/SL/TPs)", group = G_VIS) showDash = input.bool(true, "Show dashboard", group = G_DSH) dashPosIn = input.string("Top right", "Dashboard position", options = ["Top left", "Top right", "Bottom left", "Bottom right", "Middle right"], group = G_DSH) dashSizeIn = input.string("Small", "Dashboard text size", options = ["Tiny", "Small", "Normal", "Large"], group = G_DSH, tooltip = "Resizes the whole dashboard.") color cBull = themeIn == "Custom" ? cBullIn : themeIn == "Light" ? #089981 : #26A69A color cBear = themeIn == "Custom" ? cBearIn : themeIn == "Light" ? #F23645 : #EF5350 color cZone = themeIn == "Custom" ? cZoneIn : themeIn == "Light" ? #9598A1 : #787B86 color cAccent = themeIn == "Custom" ? cAccentIn : themeIn == "Light" ? #B8860B : #F7C548 color cText = themeIn == "Custom" ? cTextIn : themeIn == "Light" ? #131722 : #D1D4DC color cPanel = themeIn == "Custom" ? cPanelIn : themeIn == "Light" ? #F0F3FA : #131722 f_size(string s) => s == "Tiny" ? size.tiny : s == "Small" ? size.small : s == "Large" ? size.large : size.normal f_pos(string s) => s == "Top left" ? position.top_left : s == "Bottom left" ? position.bottom_left : s == "Bottom right" ? position.bottom_right : s == "Middle right" ? position.middle_right : position.top_right f_vol(float v) => float a = math.abs(v) string s = a >= 1e9 ? str.tostring(v / 1e9, "#.##") + "B" : a >= 1e6 ? str.tostring(v / 1e6, "#.##") + "M" : a >= 1e3 ? str.tostring(v / 1e3, "#.#") + "K" : str.tostring(v, "#.##") s // ── zones + level lines (redrawn on the last bar only) ─────────────────────── var array zBoxes = array.new() var array lvLines = array.new() var array