SMC
by MeMyselfAndI · 1 in use · published 3 months ago
Source
sha256 8123c902a8009c04
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' ? 9 : name === 'Normal' ? 13 : 11104const 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)179180// Each call sharing a symbol/resolution is one foreign context, so the FVG181// fields below consume one of VP Script's four context slots, not five.182const fvgOpen = showFvg ? request.security('', fvgTimeframe, (f) => f.open) : null183const fvgHigh = showFvg ? request.security('', fvgTimeframe, (f) => f.high) : null184const fvgLow = showFvg ? request.security('', fvgTimeframe, (f) => f.low) : null185const fvgClose = showFvg ? request.security('', fvgTimeframe, (f) => f.close) : null186const fvgTime = showFvg ? request.security('', fvgTimeframe, (f) => f.bars.map((b) => b.time)) : null187188const dailyHigh = showDaily ? request.security('', '1D', (f) => f.high) : null189const dailyLow = showDaily ? request.security('', '1D', (f) => f.low) : null190const weeklyHigh = showWeekly ? request.security('', '1W', (f) => f.high) : null191const weeklyLow = showWeekly ? request.security('', '1W', (f) => f.low) : null192const monthlyHigh = showMonthly ? request.security('', '1M', (f) => f.high) : null193const monthlyLow = showMonthly ? request.security('', '1M', (f) => f.low) : null194195let trailingTop = null196let trailingBottom = null197let trailingTopIndex = -1198let trailingBottomIndex = -1199let trailingBaseIndex = -1200201let trueRangeTotal = 0202let fvgDeltaTotal = 0203let fvgDeltaCount = 0204let lastForeignTime = null205const completedForeignBars = []206207const highestBetween = (from, to) => {208 let value = -Infinity209 for (let i = from; i <= to; i++) if (rawHighs[i] > value) value = rawHighs[i]210 return value211}212const lowestBetween = (from, to) => {213 let value = Infinity214 for (let i = from; i <= to; i++) if (rawLows[i] < value) value = rawLows[i]215 return value216}217218const updatePivotFamily = (i, size, family, equalOnly, internal) => {219 if (i < size) return family.leg220221 const candidate = i - size222 if (!finite(rawHighs[candidate]) || !finite(rawLows[candidate])) return family.leg223 let recentHigh = -Infinity224 let recentLow = Infinity225 for (let j = candidate + 1; j <= i; j++) {226 if (finite(rawHighs[j]) && rawHighs[j] > recentHigh) recentHigh = rawHighs[j]227 if (finite(rawLows[j]) && rawLows[j] < recentLow) recentLow = rawLows[j]228 }229 if (!finite(recentHigh) || !finite(recentLow)) return family.leg230231 let nextLeg = family.leg232 if (rawHighs[candidate] > recentHigh) nextLeg = 0233 else if (rawLows[candidate] < recentLow) nextLeg = 1234 if (nextLeg === family.leg) return nextLeg235236 const isLow = nextLeg === 1237 const pivot = isLow238 ? (equalOnly ? equalLow : internal ? internalLow : swingLow)239 : (equalOnly ? equalHigh : internal ? internalHigh : swingHigh)240 const level = isLow ? rawLows[candidate] : rawHighs[candidate]241242 if (equalOnly && finite(pivot.current) && finite(atr[i]) &&243 Math.abs(pivot.current - level) < equalThreshold * atr[i]) {244 const tag = isLow ? 'EQL' : 'EQH'245 const color = isLow ? swingBull : swingBear246 const middle = Math.round((pivot.index + candidate) / 2)247 rememberLine(tag, {248 from: { index: pivot.index, price: pivot.current },249 to: { index: candidate, price: level },250 options: { color, width: 1, lineStyle: 'dotted' },251 })252 rememberLabel(tag, {253 index: middle, price: level, text: tag,254 options: { color, background: '#00000000', size: labelSize(structureLabelSize), align: 'center' },255 })256 alertSeries[isLow ? 'equalLows' : 'equalHighs'][i] = 1257 }258259 pivot.last = pivot.current260 pivot.current = level261 pivot.crossed = false262 pivot.index = candidate263264 if (!equalOnly && !internal) {265 trailingBaseIndex = candidate266 if (isLow) {267 trailingBottom = level268 trailingBottomIndex = candidate269 } else {270 trailingTop = level271 trailingTopIndex = candidate272 }273 if (showSwings) {274 const tag = isLow275 ? (finite(pivot.last) && level < pivot.last ? 'LL' : 'HL')276 : (finite(pivot.last) && level > pivot.last ? 'HH' : 'LH')277 rememberLabel(isLow ? 'swing-low' : 'swing-high', {278 index: candidate, price: level, text: tag,279 options: {280 color: isLow ? swingBull : swingBear,281 background: '#00000000', size: 10, align: 'center',282 },283 })284 }285 }286 return nextLeg287}288289const storeOrderBlock = (pivot, internal, bias, currentIndex) => {290 if (pivot.index < 0 || pivot.index >= currentIndex) return291 let chosen = -1292 if (bias === BEARISH) {293 let best = -Infinity294 for (let i = pivot.index; i < currentIndex; i++) {295 if (!finite(parsedHighs[i]) || !finite(parsedLows[i])) continue296 if (parsedHighs[i] > best) { best = parsedHighs[i]; chosen = i }297 }298 } else {299 let best = Infinity300 for (let i = pivot.index; i < currentIndex; i++) {301 if (!finite(parsedHighs[i]) || !finite(parsedLows[i])) continue302 if (parsedLows[i] < best) { best = parsedLows[i]; chosen = i }303 }304 }305 if (chosen < 0 || !finite(parsedHighs[chosen]) || !finite(parsedLows[chosen])) return306 const target = internal ? internalOrderBlocks : swingOrderBlocks307 target.unshift({ high: parsedHighs[chosen], low: parsedLows[chosen], index: chosen, bias })308 if (target.length > 100) target.length = 100309}310311const drawStructureEvent = (pivot, tag, color, internal, bullish, i) => {312 const channel = (internal ? 'internal-' : 'swing-') + (bullish ? 'bull' : 'bear')313 rememberLine(channel, {314 from: { index: pivot.index, price: pivot.current },315 to: { index: i, price: pivot.current },316 options: { color, width: 1, lineStyle: internal ? 'dashed' : 'solid' },317 })318 rememberLabel(channel, {319 index: Math.round((pivot.index + i) / 2), price: pivot.current, text: tag,320 options: {321 color, background: '#00000000',322 size: labelSize(structureLabelSize), align: 'center',323 },324 })325}326327const processStructure = (i, internal) => {328 const highPivot = internal ? internalHigh : swingHigh329 const lowPivot = internal ? internalLow : swingLow330 const previousClose = i > 0 ? bars[i - 1].close : null331 const upperWick = bars[i].high - Math.max(bars[i].close, bars[i].open)332 const lowerWick = Math.min(bars[i].close, bars[i].open) - bars[i].low333 const bullishBar = !internalConfluence || upperWick > lowerWick334 const bearishBar = !internalConfluence || upperWick < lowerWick335336 const highPrevious = highPivot.seriesPrev337 const bullCross = finite(highPivot.current) && finite(highPrevious) && finite(previousClose) &&338 bars[i].close > highPivot.current && previousClose <= highPrevious && !highPivot.crossed &&339 (!internal || (highPivot.current !== swingHigh.current && bullishBar))340341 if (bullCross) {342 const trendBefore = internal ? internalTrend : swingTrend343 const tag = trendBefore === BEARISH ? 'CHoCH' : 'BOS'344 highPivot.crossed = true345 if (internal) internalTrend = BULLISH346 else swingTrend = BULLISH347 alertSeries[(internal ? 'internal' : 'swing') + 'Bullish' + tag][i] = 1348349 const display = internal350 ? showInternals && accepts(internalBullMode, tag)351 : showStructure && accepts(swingBullMode, tag)352 if (display) drawStructureEvent(highPivot, tag, internal ? internalBull : swingBull, internal, true, i)353 if ((internal && showInternalOb) || (!internal && showSwingOb)) {354 storeOrderBlock(highPivot, internal, BULLISH, i)355 }356 }357358 const lowPrevious = lowPivot.seriesPrev359 const bearCross = finite(lowPivot.current) && finite(lowPrevious) && finite(previousClose) &&360 bars[i].close < lowPivot.current && previousClose >= lowPrevious && !lowPivot.crossed &&361 (!internal || (lowPivot.current !== swingLow.current && bearishBar))362363 if (bearCross) {364 const trendBefore = internal ? internalTrend : swingTrend365 const tag = trendBefore === BULLISH ? 'CHoCH' : 'BOS'366 lowPivot.crossed = true367 if (internal) internalTrend = BEARISH368 else swingTrend = BEARISH369 alertSeries[(internal ? 'internal' : 'swing') + 'Bearish' + tag][i] = 1370371 const display = internal372 ? showInternals && accepts(internalBearMode, tag)373 : showStructure && accepts(swingBearMode, tag)374 if (display) drawStructureEvent(lowPivot, tag, internal ? internalBear : swingBear, internal, false, i)375 if ((internal && showInternalOb) || (!internal && showSwingOb)) {376 storeOrderBlock(lowPivot, internal, BEARISH, i)377 }378 }379}380381// ─────────────────────────────────────────────────────────────────────────────382// One chronological pass383// ─────────────────────────────────────────────────────────────────────────────384385for (let i = 0; i < barCount; i++) {386 const b = bars[i]387 const validBar = finite(b.open) && finite(b.high) && finite(b.low) && finite(b.close)388 rawHighs.push(validBar ? b.high : null)389 rawLows.push(validBar ? b.low : null)390391 // Malformed feed bars must remain gaps. Letting Infinity enter the running392 // range or order-block search poisons all later state and can produce an393 // invalid drawing corner.394 if (!validBar) {395 parsedHighs.push(null)396 parsedLows.push(null)397 trendSeries[i] = null398 continue399 }400401 const previousClose = i > 0 && finite(bars[i - 1].close) ? bars[i - 1].close : b.close402 const tr = Math.max(b.high - b.low, Math.abs(b.high - previousClose), Math.abs(b.low - previousClose))403 trueRangeTotal += tr404 const cumulativeRange = trueRangeTotal / Math.max(1, i)405 const volatility = obFilter === 'Atr' ? atr[i] : cumulativeRange406 const highVolatility = finite(volatility) && (b.high - b.low) >= 2 * volatility407 parsedHighs.push(highVolatility ? b.low : b.high)408 parsedLows.push(highVolatility ? b.high : b.low)409410 if (!finite(trailingTop) || b.high > trailingTop) {411 trailingTop = b.high412 trailingTopIndex = i413 }414 if (!finite(trailingBottom) || b.low < trailingBottom) {415 trailingBottom = b.low416 trailingBottomIndex = i417 }418419 // Pine colours the current candle with the bias known before this bar's420 // close produces any new structure event.421 trendSeries[i] = internalTrend422 if (colorCandles) candleColors[i] = internalTrend === BULLISH ? swingBull : swingBear423424 swingLeg = updatePivotFamily(i, swingsLength, { leg: swingLeg }, false, false)425 internalLeg = updatePivotFamily(i, 5, { leg: internalLeg }, false, true)426 if (showEqual) equalLeg = updatePivotFamily(i, equalLength, { leg: equalLeg }, true, false)427428 if (showInternals || showInternalOb || colorCandles) processStructure(i, true)429 if (showStructure || showSwingOb || showStrongWeak) processStructure(i, false)430431 // Order-block mitigation. A removal is also the corresponding breakout432 // alert, matching the source indicator's alert semantics.433 const bearishSource = obMitigation === 'Close' ? b.close : b.high434 const bullishSource = obMitigation === 'Close' ? b.close : b.low435 for (const pair of [[internalOrderBlocks, true], [swingOrderBlocks, false]]) {436 const blocks = pair[0]437 const internal = pair[1]438 for (let j = blocks.length - 1; j >= 0; j--) {439 const ob = blocks[j]440 const crossed = (ob.bias === BEARISH && bearishSource > ob.high) ||441 (ob.bias === BULLISH && bullishSource < ob.low)442 if (!crossed) continue443 blocks.splice(j, 1)444 const key = (internal ? 'internal' : 'swing') +445 (ob.bias === BULLISH ? 'Bullish' : 'Bearish') + 'OrderBlock'446 alertSeries[key][i] = 1447 }448 }449450 // Existing fair-value gaps are removed before a new completed foreign bar451 // may create one, mirroring Pine's execution order.452 if (showFvg) {453 for (let j = fairValueGaps.length - 1; j >= 0; j--) {454 const gap = fairValueGaps[j]455 if ((gap.bias === BULLISH && b.low < gap.bottom) ||456 (gap.bias === BEARISH && b.high > gap.top)) fairValueGaps.splice(j, 1)457 }458459 const ft = fvgTime[i]460 if (finite(ft) && ft !== lastForeignTime && finite(fvgOpen[i]) && finite(fvgHigh[i]) &&461 finite(fvgLow[i]) && finite(fvgClose[i])) {462 completedForeignBars.push({463 time: ft, open: fvgOpen[i], high: fvgHigh[i], low: fvgLow[i], close: fvgClose[i], index: i,464 })465 lastForeignTime = ft466 if (completedForeignBars.length >= 3) {467 const current = completedForeignBars[completedForeignBars.length - 1]468 const previous = completedForeignBars[completedForeignBars.length - 2]469 const twoBack = completedForeignBars[completedForeignBars.length - 3]470 const delta = previous.open !== 0 ? (previous.close - previous.open) / (previous.open * 100) : 0471 fvgDeltaTotal += Math.abs(delta)472 fvgDeltaCount += 1473 const threshold = autoFvgThreshold ? (fvgDeltaTotal / fvgDeltaCount) * 2 : 0474 const width = Math.max(1, current.index - previous.index)475 const right = current.index + fvgExtend * width476477 if (current.low > twoBack.high && previous.close > twoBack.high && delta > threshold) {478 fairValueGaps.unshift({479 top: current.low, bottom: twoBack.high, bias: BULLISH,480 left: previous.index, right,481 })482 alertSeries.bullishFairValueGap[i] = 1483 }484 if (current.high < twoBack.low && previous.close < twoBack.low && -delta > threshold) {485 fairValueGaps.unshift({486 top: twoBack.low, bottom: current.high, bias: BEARISH,487 left: previous.index, right,488 })489 alertSeries.bearishFairValueGap[i] = 1490 }491 if (fairValueGaps.length > 100) fairValueGaps.length = 100492 }493 }494 }495496 // The previous value of a mutable pivot is the value Pine's crossover sees497 // through the pivot series' [1] history on the next bar.498 for (const pivot of [swingHigh, swingLow, internalHigh, internalLow, equalHigh, equalLow]) {499 pivot.seriesPrev = pivot.current500 }501}502503// ─────────────────────────────────────────────────────────────────────────────504// Current objects: order blocks, FVGs, strong/weak levels and zones505// ─────────────────────────────────────────────────────────────────────────────506507const lastIndex = Math.max(0, barCount - 1)508509const drawOrderBlocks = (blocks, count, internal) => {510 const visible = blocks.slice(0, count)511 for (const ob of visible) {512 const base = internal513 ? (ob.bias === BEARISH ? internalBearObInput : internalBullObInput)514 : (ob.bias === BEARISH ? swingBearObInput : swingBullObInput)515 const color = withAlpha(516 style === 'Monochrome' ? (ob.bias === BEARISH ? MONO_BEARISH : MONO_BULLISH) : base,517 '33',518 )519 emitBox(520 { index: ob.index, price: ob.high },521 { index: lastIndex, price: ob.low },522 { color: internal ? '#00000000' : color, fillColor: color },523 )524 }525}526527if (barCount > 0 && showInternalOb) drawOrderBlocks(internalOrderBlocks, internalObCount, true)528if (barCount > 0 && showSwingOb) drawOrderBlocks(swingOrderBlocks, swingObCount, false)529530if (barCount > 0 && showFvg) {531 for (const gap of fairValueGaps) {532 const middle = (gap.top + gap.bottom) / 2533 const color = gap.bias === BULLISH ? bullishFvgColor : bearishFvgColor534 emitBox(535 { index: gap.left, price: gap.top },536 { index: gap.right, price: middle },537 { color, fillColor: color },538 )539 emitBox(540 { index: gap.left, price: middle },541 { index: gap.right, price: gap.bottom },542 { color, fillColor: color },543 )544 }545}546547if (barCount > 0 && showStrongWeak && finite(trailingTop) && finite(trailingBottom)) {548 line(549 { index: trailingTopIndex, price: trailingTop },550 { index: lastIndex, price: trailingTop },551 { color: swingBear, width: 1, extend: true },552 )553 label(lastIndex, trailingTop, swingTrend === BEARISH ? 'Strong High' : 'Weak High', {554 color: swingBear, background: '#00000000', size: 9, align: 'right',555 })556 line(557 { index: trailingBottomIndex, price: trailingBottom },558 { index: lastIndex, price: trailingBottom },559 { color: swingBull, width: 1, extend: true },560 )561 label(lastIndex, trailingBottom, swingTrend === BULLISH ? 'Strong Low' : 'Weak Low', {562 color: swingBull, background: '#00000000', size: 9, align: 'right',563 })564}565566if (barCount > 0 && showZones && finite(trailingTop) && finite(trailingBottom)) {567 const start = Math.max(0, trailingBaseIndex)568 const equilibrium = (trailingTop + trailingBottom) / 2569 const premiumBottom = 0.95 * trailingTop + 0.05 * trailingBottom570 const equilibriumTop = 0.525 * trailingTop + 0.475 * trailingBottom571 const equilibriumBottom = 0.525 * trailingBottom + 0.475 * trailingTop572 const discountTop = 0.95 * trailingBottom + 0.05 * trailingTop573574 emitBox({ index: start, price: trailingTop }, { index: lastIndex, price: premiumBottom }, {575 color: '#00000000', fillColor: withAlpha(premiumColor, '33'), text: 'Premium',576 })577 emitBox({ index: start, price: equilibriumTop }, { index: lastIndex, price: equilibriumBottom }, {578 color: '#00000000', fillColor: withAlpha(equilibriumInput, '33'), text: 'Equilibrium',579 })580 emitBox({ index: start, price: discountTop }, { index: lastIndex, price: trailingBottom }, {581 color: '#00000000', fillColor: withAlpha(discountColor, '33'), text: 'Discount',582 })583}584585// The causal security API exposes the last completed HTF high/low. Locate the586// period that produced it on the chart, then anchor the line to the actual587// extreme instead of pretending the level began at the current period open.588const drawPreviousLevels = (highSeries, lowSeries, prefix, color, styleName) => {589 if (barCount === 0 || !highSeries || !lowSeries) return590 let end = barCount - 1591 while (end >= 0 && (!finite(highSeries[end]) || !finite(lowSeries[end]))) end--592 if (end < 0) return593594 const currentHigh = highSeries[end]595 const currentLow = lowSeries[end]596 let currentStart = end597 while (currentStart > 0 && same(highSeries[currentStart - 1], currentHigh) &&598 same(lowSeries[currentStart - 1], currentLow)) currentStart--599600 let previousStart = currentStart - 1601 if (previousStart < 0) return602 const olderHigh = highSeries[previousStart]603 const olderLow = lowSeries[previousStart]604 while (previousStart > 0 && same(highSeries[previousStart - 1], olderHigh) &&605 same(lowSeries[previousStart - 1], olderLow)) previousStart--606607 const from = previousStart608 const to = Math.max(previousStart, currentStart - 1)609 let highIndex = from610 let lowIndex = from611 for (let i = from; i <= to; i++) {612 if (bars[i].high > bars[highIndex].high) highIndex = i613 if (bars[i].low < bars[lowIndex].low) lowIndex = i614 }615616 line({ index: highIndex, price: currentHigh }, { index: lastIndex, price: currentHigh }, {617 color, width: 1, lineStyle: lineStyle(styleName), extend: true,618 })619 label(lastIndex, currentHigh, 'P' + prefix + 'H', {620 color, background: '#00000000', size: 10, align: 'right',621 })622 line({ index: lowIndex, price: currentLow }, { index: lastIndex, price: currentLow }, {623 color, width: 1, lineStyle: lineStyle(styleName), extend: true,624 })625 label(lastIndex, currentLow, 'P' + prefix + 'L', {626 color, background: '#00000000', size: 10, align: 'right',627 })628}629630if (showDaily) drawPreviousLevels(dailyHigh, dailyLow, 'D', dailyColor, mtfStyle)631if (showWeekly) drawPreviousLevels(weeklyHigh, weeklyLow, 'W', weeklyColor, mtfStyle)632if (showMonthly) drawPreviousLevels(monthlyHigh, monthlyLow, 'M', monthlyColor, mtfStyle)633634// Reserve room below VP Script's hard 500-line/500-label caps for the fixed635// levels above. Long histories keep their newest structure drawings.636const eventLines = mode === 'Present' ? [...presentLines.values()] : historicalLines.slice(-470)637const eventLabels = mode === 'Present' ? [...presentLabels.values()] : historicalLabels.slice(-470)638for (const item of eventLines) line(item.from, item.to, item.options)639for (const item of eventLabels) label(item.index, item.price, item.text, item.options)640641if (colorCandles) barcolor(candleColors)642plot(trendSeries, { title: 'Internal Trend', color: '#00000000', ignoreScale: true })643644// ─────────────────────────────────────────────────────────────────────────────645// Alerts — same sixteen conditions as the source indicator646// ─────────────────────────────────────────────────────────────────────────────647648alertcondition(alertSeries.internalBullishBOS, { title: 'Internal Bullish BOS', message: 'Internal Bullish BOS formed' })649alertcondition(alertSeries.internalBullishCHoCH, { title: 'Internal Bullish CHoCH', message: 'Internal Bullish CHoCH formed' })650alertcondition(alertSeries.internalBearishBOS, { title: 'Internal Bearish BOS', message: 'Internal Bearish BOS formed' })651alertcondition(alertSeries.internalBearishCHoCH, { title: 'Internal Bearish CHoCH', message: 'Internal Bearish CHoCH formed' })652alertcondition(alertSeries.swingBullishBOS, { title: 'Bullish BOS', message: 'Bullish BOS formed' })653alertcondition(alertSeries.swingBullishCHoCH, { title: 'Bullish CHoCH', message: 'Bullish CHoCH formed' })654alertcondition(alertSeries.swingBearishBOS, { title: 'Bearish BOS', message: 'Bearish BOS formed' })655alertcondition(alertSeries.swingBearishCHoCH, { title: 'Bearish CHoCH', message: 'Bearish CHoCH formed' })656alertcondition(alertSeries.internalBullishOrderBlock, { title: 'Bullish Internal OB Breakout', message: 'Price broke bullish internal OB' })657alertcondition(alertSeries.internalBearishOrderBlock, { title: 'Bearish Internal OB Breakout', message: 'Price broke bearish internal OB' })658alertcondition(alertSeries.swingBullishOrderBlock, { title: 'Bullish Swing OB Breakout', message: 'Price broke bullish swing OB' })659alertcondition(alertSeries.swingBearishOrderBlock, { title: 'Bearish Swing OB Breakout', message: 'Price broke bearish swing OB' })660alertcondition(alertSeries.equalHighs, { title: 'Equal Highs', message: 'Equal highs detected' })661alertcondition(alertSeries.equalLows, { title: 'Equal Lows', message: 'Equal lows detected' })662alertcondition(alertSeries.bullishFairValueGap, { title: 'Bullish FVG', message: 'Bullish FVG formed' })663alertcondition(alertSeries.bearishFairValueGap, { title: 'Bearish FVG', message: 'Bearish FVG formed' })664Versions
- v1current8123c9023 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.