SMC
by MeMyselfAndI · 2 in use · published 3 months ago
Source
sha256 3f2af8d75066d716
1// Smart Money Concepts — VP Script / VerticalView TypeScript port.2//3// Adapted from "Smart Money Concepts [LuxAlgo]" (Pine Script v5).4// Original work © LuxAlgo, licensed CC BY-NC-SA 4.0:5// https://creativecommons.org/licenses/by-nc-sa/4.0/6// This adaptation is distributed under the same licence.7//8// VP Script runs once over the complete bar array. State that Pine keeps in9// `var` values is therefore ordinary local state inside the loop below.10// Drawings are emitted declaratively after the calculation; they are never11// mutated. The port deliberately uses no Intl or browser globals.1213indicator('Smart Money Concepts [LuxAlgo] — VP', {14 shortTitle: 'SMC — VP',15 pane: 'overlay',16})1718// ─────────────────────────────────────────────────────────────────────────────19// Inputs20// ─────────────────────────────────────────────────────────────────────────────2122const mode = input.string('Mode', 'Historical', { options: ['Historical', 'Present'] })23const style = input.string('Style', 'Colored', { options: ['Colored', 'Monochrome'] })24const colorCandles = input.bool('Color Candles', false)2526const showInternals = input.bool('Show Internal Structure', true)27const internalBullMode = input.string('Internal Bullish Structure', 'All', { options: ['All', 'BOS', 'CHoCH'] })28const internalBearMode = input.string('Internal Bearish Structure', 'All', { options: ['All', 'BOS', 'CHoCH'] })29const internalBullInput = input.color('Internal Bullish Color', '#089981')30const internalBearInput = input.color('Internal Bearish Color', '#F23645')31const internalConfluence = input.bool('Internal Confluence Filter', false)32const structureLabelSize = input.string('Structure Label Size', 'Small', { options: ['Tiny', 'Small', 'Normal'] })3334const showStructure = input.bool('Show Swing Structure', true)35const swingBullMode = input.string('Swing Bullish Structure', 'All', { options: ['All', 'BOS', 'CHoCH'] })36const swingBearMode = input.string('Swing Bearish Structure', 'All', { options: ['All', 'BOS', 'CHoCH'] })37const swingBullInput = input.color('Swing Bullish Color', '#089981')38const swingBearInput = input.color('Swing Bearish Color', '#F23645')39const showSwings = input.bool('Show Swing Points', false)40const swingsLength = input.int('Swing Length', 50, { min: 10, max: 500 })41const showStrongWeak = input.bool('Show Strong/Weak High/Low', true)4243const showInternalOb = input.bool('Internal Order Blocks', true)44const internalObCount = input.int('Internal Order Block Count', 5, { min: 1, max: 20 })45const showSwingOb = input.bool('Swing Order Blocks', false)46const swingObCount = input.int('Swing Order Block Count', 5, { min: 1, max: 20 })47const obFilter = input.string('Order Block Filter', 'Atr', { options: ['Atr', 'Cumulative Mean Range'] })48const obMitigation = input.string('Order Block Mitigation', 'High/Low', { options: ['Close', 'High/Low'] })49const internalBullObInput = input.color('Internal Bullish OB', '#3179f5')50const internalBearObInput = input.color('Internal Bearish OB', '#f77c80')51const swingBullObInput = input.color('Swing Bullish OB', '#1848cc')52const swingBearObInput = input.color('Swing Bearish OB', '#b22833')5354const showEqual = input.bool('Equal High/Low', true)55const equalLength = input.int('EQH/EQL Confirmation Bars', 3, { min: 1, max: 100 })56const equalThreshold = input.float('EQH/EQL Threshold', 0.1, { min: 0, max: 0.5 })5758const showFvg = input.bool('Fair Value Gaps', false)59// VP Script requires an explicit resolution. Unlike Pine's empty timeframe,60// an empty request is rejected rather than guessed from chart state.61const fvgTimeframe = input.string('FVG Timeframe', '60', {62 options: ['1', '5', '15', '30', '60', '240', '1D', '1W'],63})64const autoFvgThreshold = input.bool('FVG Auto Threshold', true)65const bullishFvgInput = input.color('Bullish FVG', '#00ff68')66const bearishFvgInput = input.color('Bearish FVG', '#ff0008')67const fvgExtend = input.int('Extend FVG (bars)', 1, { min: 0, max: 100 })6869const showDaily = input.bool('Previous Daily High/Low', false)70const mtfStyle = input.string('MTF Level Style', 'Solid', { options: ['Solid', 'Dashed', 'Dotted'] })71const dailyColor = input.color('Daily Level Color', '#2157f3')72const showWeekly = input.bool('Previous Weekly High/Low', false)73const weeklyColor = input.color('Weekly Level Color', '#2157f3')74const showMonthly = input.bool('Previous Monthly High/Low', false)75const monthlyColor = input.color('Monthly Level Color', '#2157f3')7677const showZones = input.bool('Premium/Discount Zones', false)78const premiumInput = input.color('Premium Zone', '#F23645')79const equilibriumInput = input.color('Equilibrium Zone', '#878b94')80const discountInput = input.color('Discount Zone', '#089981')8182// ─────────────────────────────────────────────────────────────────────────────83// Constants and small helpers84// ─────────────────────────────────────────────────────────────────────────────8586const BULLISH = 187const BEARISH = -188const GREEN = '#089981'89const RED = '#F23645'90const MONO_BULLISH = '#b2b5be'91const MONO_BEARISH = '#5d606b'9293const swingBull = style === 'Monochrome' ? MONO_BULLISH : swingBullInput94const swingBear = style === 'Monochrome' ? MONO_BEARISH : swingBearInput95const internalBull = style === 'Monochrome' ? MONO_BULLISH : internalBullInput96const internalBear = style === 'Monochrome' ? MONO_BEARISH : internalBearInput97const withAlpha = (color, suffix) => color[0] === '#' && color.length === 7 ? color + suffix : color98const bullishFvgColor = withAlpha(style === 'Monochrome' ? MONO_BULLISH : bullishFvgInput, '4d')99const bearishFvgColor = withAlpha(style === 'Monochrome' ? MONO_BEARISH : bearishFvgInput, '4d')100const premiumColor = style === 'Monochrome' ? MONO_BEARISH : premiumInput101const discountColor = style === 'Monochrome' ? MONO_BULLISH : discountInput102103const labelSize = (name) => name === 'Tiny' ? 8 : name === 'Normal' ? 12 : 10104const lineStyle = (name) => name === 'Dashed' ? 'dashed' : name === 'Dotted' ? 'dotted' : 'solid'105const accepts = (choice, tag) => choice === 'All' || choice === tag106const finite = (v) => typeof v === 'number' && Number.isFinite(v)107const same = (a, b) => finite(a) && finite(b) && Math.abs(a - b) <= Math.max(1, Math.abs(a), Math.abs(b)) * 1e-12108const finitePoint = (point) => finite(point.index) && finite(point.price)109const emitBox = (from, to, options) => {110 if (!finitePoint(from) || !finitePoint(to)) return111 box(from, to, options)112}113114const makePivot = () => ({115 current: null,116 last: null,117 crossed: false,118 index: -1,119 seriesPrev: null,120})121122const swingHigh = makePivot()123const swingLow = makePivot()124const internalHigh = makePivot()125const internalLow = makePivot()126const equalHigh = makePivot()127const equalLow = makePivot()128129let swingTrend = 0130let internalTrend = 0131let swingLeg = 0132let internalLeg = 0133let equalLeg = 0134135const parsedHighs = []136const parsedLows = []137const rawHighs = []138const rawLows = []139const internalOrderBlocks = []140const swingOrderBlocks = []141const fairValueGaps = []142143const historicalLines = []144const historicalLabels = []145const presentLines = new Map()146const presentLabels = new Map()147148const rememberLine = (key, value) => {149 if (mode === 'Present') presentLines.set(key, value)150 else historicalLines.push(value)151}152const rememberLabel = (key, value) => {153 if (mode === 'Present') presentLabels.set(key, value)154 else historicalLabels.push(value)155}156157const alertSeries = {158 internalBullishBOS: new Array(barCount).fill(0),159 internalBullishCHoCH: new Array(barCount).fill(0),160 internalBearishBOS: new Array(barCount).fill(0),161 internalBearishCHoCH: new Array(barCount).fill(0),162 swingBullishBOS: new Array(barCount).fill(0),163 swingBullishCHoCH: new Array(barCount).fill(0),164 swingBearishBOS: new Array(barCount).fill(0),165 swingBearishCHoCH: new Array(barCount).fill(0),166 internalBullishOrderBlock: new Array(barCount).fill(0),167 internalBearishOrderBlock: new Array(barCount).fill(0),168 swingBullishOrderBlock: new Array(barCount).fill(0),169 swingBearishOrderBlock: new Array(barCount).fill(0),170 equalHighs: new Array(barCount).fill(0),171 equalLows: new Array(barCount).fill(0),172 bullishFairValueGap: new Array(barCount).fill(0),173 bearishFairValueGap: new Array(barCount).fill(0),174}175176const trendSeries = new Array(barCount).fill(null)177const candleColors = new Array(barCount).fill(null)178const atr = ta.atr(200)179const LABEL_SPACING_ATR = 0.24180181// VP labels are anchored directly at a price; unlike Pine's label_up/down182// styles they do not automatically clear a horizontal line. Offset labels by183// volatility so the spacing remains readable across instruments and zooms.184const displacedLabelPrice = (price, index, above, multiplier) => {185 if (!finite(price)) return price186 let range = index >= 0 && index < barCount && finite(atr[index]) ? atr[index] : null187 if (!finite(range) || range <= 0) {188 const b = index >= 0 && index < barCount ? bars[index] : null189 range = b && finite(b.high) && finite(b.low) ? Math.abs(b.high - b.low) : null190 }191 if (!finite(range) || range <= 0) range = Math.max(Math.abs(price) * 0.002, 0.000001)192 const padding = range * LABEL_SPACING_ATR * multiplier193 return above ? price + padding : price - padding194}195196// Each call sharing a symbol/resolution is one foreign context, so the FVG197// fields below consume one of VP Script's four context slots, not five.198const fvgOpen = showFvg ? request.security('', fvgTimeframe, (f) => f.open) : null199const fvgHigh = showFvg ? request.security('', fvgTimeframe, (f) => f.high) : null200const fvgLow = showFvg ? request.security('', fvgTimeframe, (f) => f.low) : null201const fvgClose = showFvg ? request.security('', fvgTimeframe, (f) => f.close) : null202const fvgTime = showFvg ? request.security('', fvgTimeframe, (f) => f.bars.map((b) => b.time)) : null203204const dailyHigh = showDaily ? request.security('', '1D', (f) => f.high) : null205const dailyLow = showDaily ? request.security('', '1D', (f) => f.low) : null206const weeklyHigh = showWeekly ? request.security('', '1W', (f) => f.high) : null207const weeklyLow = showWeekly ? request.security('', '1W', (f) => f.low) : null208const monthlyHigh = showMonthly ? request.security('', '1M', (f) => f.high) : null209const monthlyLow = showMonthly ? request.security('', '1M', (f) => f.low) : null210211let trailingTop = null212let trailingBottom = null213let trailingTopIndex = -1214let trailingBottomIndex = -1215let trailingBaseIndex = -1216217let trueRangeTotal = 0218let fvgDeltaTotal = 0219let fvgDeltaCount = 0220let lastForeignTime = null221const completedForeignBars = []222223const highestBetween = (from, to) => {224 let value = -Infinity225 for (let i = from; i <= to; i++) if (rawHighs[i] > value) value = rawHighs[i]226 return value227}228const lowestBetween = (from, to) => {229 let value = Infinity230 for (let i = from; i <= to; i++) if (rawLows[i] < value) value = rawLows[i]231 return value232}233234const updatePivotFamily = (i, size, family, equalOnly, internal) => {235 if (i < size) return family.leg236237 const candidate = i - size238 if (!finite(rawHighs[candidate]) || !finite(rawLows[candidate])) return family.leg239 let recentHigh = -Infinity240 let recentLow = Infinity241 for (let j = candidate + 1; j <= i; j++) {242 if (finite(rawHighs[j]) && rawHighs[j] > recentHigh) recentHigh = rawHighs[j]243 if (finite(rawLows[j]) && rawLows[j] < recentLow) recentLow = rawLows[j]244 }245 if (!finite(recentHigh) || !finite(recentLow)) return family.leg246247 let nextLeg = family.leg248 if (rawHighs[candidate] > recentHigh) nextLeg = 0249 else if (rawLows[candidate] < recentLow) nextLeg = 1250 if (nextLeg === family.leg) return nextLeg251252 const isLow = nextLeg === 1253 const pivot = isLow254 ? (equalOnly ? equalLow : internal ? internalLow : swingLow)255 : (equalOnly ? equalHigh : internal ? internalHigh : swingHigh)256 const level = isLow ? rawLows[candidate] : rawHighs[candidate]257258 if (equalOnly && finite(pivot.current) && finite(atr[i]) &&259 Math.abs(pivot.current - level) < equalThreshold * atr[i]) {260 const tag = isLow ? 'EQL' : 'EQH'261 const color = isLow ? swingBull : swingBear262 const middle = Math.round((pivot.index + candidate) / 2)263 rememberLine(tag, {264 from: { index: pivot.index, price: pivot.current },265 to: { index: candidate, price: level },266 options: { color, width: 1, lineStyle: 'dotted' },267 })268 rememberLabel(tag, {269 index: middle, price: displacedLabelPrice(level, middle, !isLow, 1.6), text: tag,270 options: { color, background: '#00000000', size: labelSize(structureLabelSize), align: 'center' },271 })272 alertSeries[isLow ? 'equalLows' : 'equalHighs'][i] = 1273 }274275 pivot.last = pivot.current276 pivot.current = level277 pivot.crossed = false278 pivot.index = candidate279280 if (!equalOnly && !internal) {281 trailingBaseIndex = candidate282 if (isLow) {283 trailingBottom = level284 trailingBottomIndex = candidate285 } else {286 trailingTop = level287 trailingTopIndex = candidate288 }289 if (showSwings) {290 const tag = isLow291 ? (finite(pivot.last) && level < pivot.last ? 'LL' : 'HL')292 : (finite(pivot.last) && level > pivot.last ? 'HH' : 'LH')293 rememberLabel(isLow ? 'swing-low' : 'swing-high', {294 index: candidate, price: displacedLabelPrice(level, candidate, !isLow, 1), text: tag,295 options: {296 color: isLow ? swingBull : swingBear,297 background: '#00000000', size: 9, align: 'center',298 },299 })300 }301 }302 return nextLeg303}304305const storeOrderBlock = (pivot, internal, bias, currentIndex) => {306 if (pivot.index < 0 || pivot.index >= currentIndex) return307 let chosen = -1308 if (bias === BEARISH) {309 let best = -Infinity310 for (let i = pivot.index; i < currentIndex; i++) {311 if (!finite(parsedHighs[i]) || !finite(parsedLows[i])) continue312 if (parsedHighs[i] > best) { best = parsedHighs[i]; chosen = i }313 }314 } else {315 let best = Infinity316 for (let i = pivot.index; i < currentIndex; i++) {317 if (!finite(parsedHighs[i]) || !finite(parsedLows[i])) continue318 if (parsedLows[i] < best) { best = parsedLows[i]; chosen = i }319 }320 }321 if (chosen < 0 || !finite(parsedHighs[chosen]) || !finite(parsedLows[chosen])) return322 const target = internal ? internalOrderBlocks : swingOrderBlocks323 target.unshift({ high: parsedHighs[chosen], low: parsedLows[chosen], index: chosen, bias })324 if (target.length > 100) target.length = 100325}326327const drawStructureEvent = (pivot, tag, color, internal, bullish, i) => {328 const channel = (internal ? 'internal-' : 'swing-') + (bullish ? 'bull' : 'bear')329 rememberLine(channel, {330 from: { index: pivot.index, price: pivot.current },331 to: { index: i, price: pivot.current },332 options: { color, width: 1, lineStyle: internal ? 'dashed' : 'solid' },333 })334 rememberLabel(channel, {335 index: Math.round((pivot.index + i) / 2),336 price: displacedLabelPrice(pivot.current, Math.round((pivot.index + i) / 2), bullish, 1),337 text: tag,338 options: {339 color, background: '#00000000',340 size: labelSize(structureLabelSize), align: 'center',341 },342 })343}344345const processStructure = (i, internal) => {346 const highPivot = internal ? internalHigh : swingHigh347 const lowPivot = internal ? internalLow : swingLow348 const previousClose = i > 0 ? bars[i - 1].close : null349 const upperWick = bars[i].high - Math.max(bars[i].close, bars[i].open)350 const lowerWick = Math.min(bars[i].close, bars[i].open) - bars[i].low351 const bullishBar = !internalConfluence || upperWick > lowerWick352 const bearishBar = !internalConfluence || upperWick < lowerWick353354 const highPrevious = highPivot.seriesPrev355 const bullCross = finite(highPivot.current) && finite(highPrevious) && finite(previousClose) &&356 bars[i].close > highPivot.current && previousClose <= highPrevious && !highPivot.crossed &&357 (!internal || (highPivot.current !== swingHigh.current && bullishBar))358359 if (bullCross) {360 const trendBefore = internal ? internalTrend : swingTrend361 const tag = trendBefore === BEARISH ? 'CHoCH' : 'BOS'362 highPivot.crossed = true363 if (internal) internalTrend = BULLISH364 else swingTrend = BULLISH365 alertSeries[(internal ? 'internal' : 'swing') + 'Bullish' + tag][i] = 1366367 const display = internal368 ? showInternals && accepts(internalBullMode, tag)369 : showStructure && accepts(swingBullMode, tag)370 if (display) drawStructureEvent(highPivot, tag, internal ? internalBull : swingBull, internal, true, i)371 if ((internal && showInternalOb) || (!internal && showSwingOb)) {372 storeOrderBlock(highPivot, internal, BULLISH, i)373 }374 }375376 const lowPrevious = lowPivot.seriesPrev377 const bearCross = finite(lowPivot.current) && finite(lowPrevious) && finite(previousClose) &&378 bars[i].close < lowPivot.current && previousClose >= lowPrevious && !lowPivot.crossed &&379 (!internal || (lowPivot.current !== swingLow.current && bearishBar))380381 if (bearCross) {382 const trendBefore = internal ? internalTrend : swingTrend383 const tag = trendBefore === BULLISH ? 'CHoCH' : 'BOS'384 lowPivot.crossed = true385 if (internal) internalTrend = BEARISH386 else swingTrend = BEARISH387 alertSeries[(internal ? 'internal' : 'swing') + 'Bearish' + tag][i] = 1388389 const display = internal390 ? showInternals && accepts(internalBearMode, tag)391 : showStructure && accepts(swingBearMode, tag)392 if (display) drawStructureEvent(lowPivot, tag, internal ? internalBear : swingBear, internal, false, i)393 if ((internal && showInternalOb) || (!internal && showSwingOb)) {394 storeOrderBlock(lowPivot, internal, BEARISH, i)395 }396 }397}398399// ─────────────────────────────────────────────────────────────────────────────400// One chronological pass401// ─────────────────────────────────────────────────────────────────────────────402403for (let i = 0; i < barCount; i++) {404 const b = bars[i]405 const validBar = finite(b.open) && finite(b.high) && finite(b.low) && finite(b.close)406 rawHighs.push(validBar ? b.high : null)407 rawLows.push(validBar ? b.low : null)408409 // Malformed feed bars must remain gaps. Letting Infinity enter the running410 // range or order-block search poisons all later state and can produce an411 // invalid drawing corner.412 if (!validBar) {413 parsedHighs.push(null)414 parsedLows.push(null)415 trendSeries[i] = null416 continue417 }418419 const previousClose = i > 0 && finite(bars[i - 1].close) ? bars[i - 1].close : b.close420 const tr = Math.max(b.high - b.low, Math.abs(b.high - previousClose), Math.abs(b.low - previousClose))421 trueRangeTotal += tr422 const cumulativeRange = trueRangeTotal / Math.max(1, i)423 const volatility = obFilter === 'Atr' ? atr[i] : cumulativeRange424 const highVolatility = finite(volatility) && (b.high - b.low) >= 2 * volatility425 parsedHighs.push(highVolatility ? b.low : b.high)426 parsedLows.push(highVolatility ? b.high : b.low)427428 if (!finite(trailingTop) || b.high > trailingTop) {429 trailingTop = b.high430 trailingTopIndex = i431 }432 if (!finite(trailingBottom) || b.low < trailingBottom) {433 trailingBottom = b.low434 trailingBottomIndex = i435 }436437 // Pine colours the current candle with the bias known before this bar's438 // close produces any new structure event.439 trendSeries[i] = internalTrend440 if (colorCandles) candleColors[i] = internalTrend === BULLISH ? swingBull : swingBear441442 swingLeg = updatePivotFamily(i, swingsLength, { leg: swingLeg }, false, false)443 internalLeg = updatePivotFamily(i, 5, { leg: internalLeg }, false, true)444 if (showEqual) equalLeg = updatePivotFamily(i, equalLength, { leg: equalLeg }, true, false)445446 if (showInternals || showInternalOb || colorCandles) processStructure(i, true)447 if (showStructure || showSwingOb || showStrongWeak) processStructure(i, false)448449 // Order-block mitigation. A removal is also the corresponding breakout450 // alert, matching the source indicator's alert semantics.451 const bearishSource = obMitigation === 'Close' ? b.close : b.high452 const bullishSource = obMitigation === 'Close' ? b.close : b.low453 for (const pair of [[internalOrderBlocks, true], [swingOrderBlocks, false]]) {454 const blocks = pair[0]455 const internal = pair[1]456 for (let j = blocks.length - 1; j >= 0; j--) {457 const ob = blocks[j]458 const crossed = (ob.bias === BEARISH && bearishSource > ob.high) ||459 (ob.bias === BULLISH && bullishSource < ob.low)460 if (!crossed) continue461 blocks.splice(j, 1)462 const key = (internal ? 'internal' : 'swing') +463 (ob.bias === BULLISH ? 'Bullish' : 'Bearish') + 'OrderBlock'464 alertSeries[key][i] = 1465 }466 }467468 // Existing fair-value gaps are removed before a new completed foreign bar469 // may create one, mirroring Pine's execution order.470 if (showFvg) {471 for (let j = fairValueGaps.length - 1; j >= 0; j--) {472 const gap = fairValueGaps[j]473 if ((gap.bias === BULLISH && b.low < gap.bottom) ||474 (gap.bias === BEARISH && b.high > gap.top)) fairValueGaps.splice(j, 1)475 }476477 const ft = fvgTime[i]478 if (finite(ft) && ft !== lastForeignTime && finite(fvgOpen[i]) && finite(fvgHigh[i]) &&479 finite(fvgLow[i]) && finite(fvgClose[i])) {480 completedForeignBars.push({481 time: ft, open: fvgOpen[i], high: fvgHigh[i], low: fvgLow[i], close: fvgClose[i], index: i,482 })483 lastForeignTime = ft484 if (completedForeignBars.length >= 3) {485 const current = completedForeignBars[completedForeignBars.length - 1]486 const previous = completedForeignBars[completedForeignBars.length - 2]487 const twoBack = completedForeignBars[completedForeignBars.length - 3]488 const delta = previous.open !== 0 ? (previous.close - previous.open) / (previous.open * 100) : 0489 fvgDeltaTotal += Math.abs(delta)490 fvgDeltaCount += 1491 const threshold = autoFvgThreshold ? (fvgDeltaTotal / fvgDeltaCount) * 2 : 0492 const width = Math.max(1, current.index - previous.index)493 const right = current.index + fvgExtend * width494495 if (current.low > twoBack.high && previous.close > twoBack.high && delta > threshold) {496 fairValueGaps.unshift({497 top: current.low, bottom: twoBack.high, bias: BULLISH,498 left: previous.index, right,499 })500 alertSeries.bullishFairValueGap[i] = 1501 }502 if (current.high < twoBack.low && previous.close < twoBack.low && -delta > threshold) {503 fairValueGaps.unshift({504 top: twoBack.low, bottom: current.high, bias: BEARISH,505 left: previous.index, right,506 })507 alertSeries.bearishFairValueGap[i] = 1508 }509 if (fairValueGaps.length > 100) fairValueGaps.length = 100510 }511 }512 }513514 // The previous value of a mutable pivot is the value Pine's crossover sees515 // through the pivot series' [1] history on the next bar.516 for (const pivot of [swingHigh, swingLow, internalHigh, internalLow, equalHigh, equalLow]) {517 pivot.seriesPrev = pivot.current518 }519}520521// ─────────────────────────────────────────────────────────────────────────────522// Current objects: order blocks, FVGs, strong/weak levels and zones523// ─────────────────────────────────────────────────────────────────────────────524525const lastIndex = Math.max(0, barCount - 1)526527const drawOrderBlocks = (blocks, count, internal) => {528 const visible = blocks.slice(0, count)529 for (const ob of visible) {530 const base = internal531 ? (ob.bias === BEARISH ? internalBearObInput : internalBullObInput)532 : (ob.bias === BEARISH ? swingBearObInput : swingBullObInput)533 const color = withAlpha(534 style === 'Monochrome' ? (ob.bias === BEARISH ? MONO_BEARISH : MONO_BULLISH) : base,535 '33',536 )537 emitBox(538 { index: ob.index, price: ob.high },539 { index: lastIndex, price: ob.low },540 { color: internal ? '#00000000' : color, fillColor: color },541 )542 }543}544545if (barCount > 0 && showInternalOb) drawOrderBlocks(internalOrderBlocks, internalObCount, true)546if (barCount > 0 && showSwingOb) drawOrderBlocks(swingOrderBlocks, swingObCount, false)547548if (barCount > 0 && showFvg) {549 for (const gap of fairValueGaps) {550 const middle = (gap.top + gap.bottom) / 2551 const color = gap.bias === BULLISH ? bullishFvgColor : bearishFvgColor552 emitBox(553 { index: gap.left, price: gap.top },554 { index: gap.right, price: middle },555 { color, fillColor: color },556 )557 emitBox(558 { index: gap.left, price: middle },559 { index: gap.right, price: gap.bottom },560 { color, fillColor: color },561 )562 }563}564565if (barCount > 0 && showStrongWeak && finite(trailingTop) && finite(trailingBottom)) {566 line(567 { index: trailingTopIndex, price: trailingTop },568 { index: lastIndex, price: trailingTop },569 { color: swingBear, width: 1, extend: true },570 )571 label(lastIndex, displacedLabelPrice(trailingTop, lastIndex, true, 1), swingTrend === BEARISH ? 'Strong High' : 'Weak High', {572 color: swingBear, background: '#00000000', size: 8, align: 'right',573 })574 line(575 { index: trailingBottomIndex, price: trailingBottom },576 { index: lastIndex, price: trailingBottom },577 { color: swingBull, width: 1, extend: true },578 )579 label(lastIndex, displacedLabelPrice(trailingBottom, lastIndex, false, 1), swingTrend === BULLISH ? 'Strong Low' : 'Weak Low', {580 color: swingBull, background: '#00000000', size: 8, align: 'right',581 })582}583584if (barCount > 0 && showZones && finite(trailingTop) && finite(trailingBottom)) {585 const start = Math.max(0, trailingBaseIndex)586 const equilibrium = (trailingTop + trailingBottom) / 2587 const premiumBottom = 0.95 * trailingTop + 0.05 * trailingBottom588 const equilibriumTop = 0.525 * trailingTop + 0.475 * trailingBottom589 const equilibriumBottom = 0.525 * trailingBottom + 0.475 * trailingTop590 const discountTop = 0.95 * trailingBottom + 0.05 * trailingTop591592 emitBox({ index: start, price: trailingTop }, { index: lastIndex, price: premiumBottom }, {593 color: '#00000000', fillColor: withAlpha(premiumColor, '33'), text: 'Premium',594 })595 emitBox({ index: start, price: equilibriumTop }, { index: lastIndex, price: equilibriumBottom }, {596 color: '#00000000', fillColor: withAlpha(equilibriumInput, '33'), text: 'Equilibrium',597 })598 emitBox({ index: start, price: discountTop }, { index: lastIndex, price: trailingBottom }, {599 color: '#00000000', fillColor: withAlpha(discountColor, '33'), text: 'Discount',600 })601}602603// The causal security API exposes the last completed HTF high/low. Locate the604// period that produced it on the chart, then anchor the line to the actual605// extreme instead of pretending the level began at the current period open.606const drawPreviousLevels = (highSeries, lowSeries, prefix, color, styleName) => {607 if (barCount === 0 || !highSeries || !lowSeries) return608 let end = barCount - 1609 while (end >= 0 && (!finite(highSeries[end]) || !finite(lowSeries[end]))) end--610 if (end < 0) return611612 const currentHigh = highSeries[end]613 const currentLow = lowSeries[end]614 let currentStart = end615 while (currentStart > 0 && same(highSeries[currentStart - 1], currentHigh) &&616 same(lowSeries[currentStart - 1], currentLow)) currentStart--617618 let previousStart = currentStart - 1619 if (previousStart < 0) return620 const olderHigh = highSeries[previousStart]621 const olderLow = lowSeries[previousStart]622 while (previousStart > 0 && same(highSeries[previousStart - 1], olderHigh) &&623 same(lowSeries[previousStart - 1], olderLow)) previousStart--624625 const from = previousStart626 const to = Math.max(previousStart, currentStart - 1)627 let highIndex = from628 let lowIndex = from629 for (let i = from; i <= to; i++) {630 if (bars[i].high > bars[highIndex].high) highIndex = i631 if (bars[i].low < bars[lowIndex].low) lowIndex = i632 }633634 line({ index: highIndex, price: currentHigh }, { index: lastIndex, price: currentHigh }, {635 color, width: 1, lineStyle: lineStyle(styleName), extend: true,636 })637 label(lastIndex, displacedLabelPrice(currentHigh, lastIndex, true, 1), 'P' + prefix + 'H', {638 color, background: '#00000000', size: 9, align: 'right',639 })640 line({ index: lowIndex, price: currentLow }, { index: lastIndex, price: currentLow }, {641 color, width: 1, lineStyle: lineStyle(styleName), extend: true,642 })643 label(lastIndex, displacedLabelPrice(currentLow, lastIndex, false, 1), 'P' + prefix + 'L', {644 color, background: '#00000000', size: 9, align: 'right',645 })646}647648if (showDaily) drawPreviousLevels(dailyHigh, dailyLow, 'D', dailyColor, mtfStyle)649if (showWeekly) drawPreviousLevels(weeklyHigh, weeklyLow, 'W', weeklyColor, mtfStyle)650if (showMonthly) drawPreviousLevels(monthlyHigh, monthlyLow, 'M', monthlyColor, mtfStyle)651652// Reserve room below VP Script's hard 500-line/500-label caps for the fixed653// levels above. Long histories keep their newest structure drawings.654const eventLines = mode === 'Present' ? [...presentLines.values()] : historicalLines.slice(-470)655const eventLabels = mode === 'Present' ? [...presentLabels.values()] : historicalLabels.slice(-470)656for (const item of eventLines) line(item.from, item.to, item.options)657for (const item of eventLabels) label(item.index, item.price, item.text, item.options)658659if (colorCandles) barcolor(candleColors)660plot(trendSeries, { title: 'Internal Trend', color: '#00000000', ignoreScale: true })661662// ─────────────────────────────────────────────────────────────────────────────663// Alerts — same sixteen conditions as the source indicator664// ─────────────────────────────────────────────────────────────────────────────665666alertcondition(alertSeries.internalBullishBOS, { title: 'Internal Bullish BOS', message: 'Internal Bullish BOS formed' })667alertcondition(alertSeries.internalBullishCHoCH, { title: 'Internal Bullish CHoCH', message: 'Internal Bullish CHoCH formed' })668alertcondition(alertSeries.internalBearishBOS, { title: 'Internal Bearish BOS', message: 'Internal Bearish BOS formed' })669alertcondition(alertSeries.internalBearishCHoCH, { title: 'Internal Bearish CHoCH', message: 'Internal Bearish CHoCH formed' })670alertcondition(alertSeries.swingBullishBOS, { title: 'Bullish BOS', message: 'Bullish BOS formed' })671alertcondition(alertSeries.swingBullishCHoCH, { title: 'Bullish CHoCH', message: 'Bullish CHoCH formed' })672alertcondition(alertSeries.swingBearishBOS, { title: 'Bearish BOS', message: 'Bearish BOS formed' })673alertcondition(alertSeries.swingBearishCHoCH, { title: 'Bearish CHoCH', message: 'Bearish CHoCH formed' })674alertcondition(alertSeries.internalBullishOrderBlock, { title: 'Bullish Internal OB Breakout', message: 'Price broke bullish internal OB' })675alertcondition(alertSeries.internalBearishOrderBlock, { title: 'Bearish Internal OB Breakout', message: 'Price broke bearish internal OB' })676alertcondition(alertSeries.swingBullishOrderBlock, { title: 'Bullish Swing OB Breakout', message: 'Price broke bullish swing OB' })677alertcondition(alertSeries.swingBearishOrderBlock, { title: 'Bearish Swing OB Breakout', message: 'Price broke bearish swing OB' })678alertcondition(alertSeries.equalHighs, { title: 'Equal Highs', message: 'Equal highs detected' })679alertcondition(alertSeries.equalLows, { title: 'Equal Lows', message: 'Equal lows detected' })680alertcondition(alertSeries.bullishFairValueGap, { title: 'Bullish FVG', message: 'Bullish FVG formed' })681alertcondition(alertSeries.bearishFairValueGap, { title: 'Bearish FVG', message: 'Bearish FVG formed' })682Versions
- v5current3f2af8d73 months ago
- v49dc30ca43 months ago
- v36e7dae543 months ago
- v1ee94b2123 months ago
This indicator runs in an isolated sandbox: it cannot reach your account, your data or the network, and if it stops responding it is stopped for you. That does not make it correct — nobody has checked whether it is any good, which is why its source is above.