Copy the code as it is. No coding needed.
View the full code
// ═══════════════════════════════════════════════════════════════════════════════════════════════════════
// ICT LEVELS DESK — NDOG · NWOG · midnight / 08:30 / 09:30 opens · PDH-PDL · PWH-PWL · PMH-PML · first-presented FVG · macro windows · Pine Script v6 · v1.2.0
// Built for a r/pinescript thread reply ("Real Trader Tim's toolkit … but clean, lightweight, only what matters").
// The wishlist: NDOG / NWOG, first-presentation FVGs, the midnight open, weekly / daily highs and lows, macro
// windows, and "days back" so the chart does not drown. This desk adds what the wishlist did not ask for:
// · every level knows its own state — a gap is untouched / entered / FILLED, a high or low is resting / SWEPT /
// BROKEN, an FVG is fresh / TOUCHED / INVERTED — and fades once it is used, or leaves the chart (switch)
// · a proximity filter: only levels within N × the daily ATR of price are drawn; the panel says how many are hidden
// · the panel lists the three nearest levels above and below price with the distance in points and ATR
// · a countdown to the next macro window, and what today's first-presented FVG did
// · base rates on the loaded chart: how often the NDOG filled the same day, PDH / PDL got taken, the FPFVG revisited
// Every level commits when it is born (a closed bar, a closed period); states change on closed bars only.
// No signals, no background tint. Context, not instruction. License: MIT — share freely, keep this header. © 2026 ProEA Lab
// ═══════════════════════════════════════════════════════════════════════════════════════════════════════
//@version=6
indicator("ICT Levels Desk — NDOG · NWOG · opens · PDH/PDL · PWH/PWL · FPFVG · macros", "ICT Levels", overlay = true, max_lines_count = 400, max_boxes_count = 200, max_labels_count = 500)
// ─────────────────────────────────────────────────────────────────── INPUTS
gT = "Clock"
inTz = input.string("America/New_York", "Timezone (IANA)", group = gT, tooltip = "Every time below is read in this timezone. ICT times are New York.", display = display.none)
gG = "Opening gaps (NDOG · NWOG)"
inNdogOn = input.bool(true, "NDOG — new-day opening gap", group = gG, tooltip = "The gap between the close at the day-close time and the open at the day-open time (16:00 → 18:00 New York for futures and most CFDs; set 16:00 → 09:30 for regular-hours stocks). Drawn as a box with its consequent encroachment (the midline).", display = display.none)
inNwogOn = input.bool(true, "NWOG — new-week opening gap", group = gG, tooltip = "The same gap across the weekend: the last close before the weekend to the first open after it. ICT keeps the last few weeks of these as reference.", display = display.none)
inCloseTime = input.string("16:00", " Day-close time (HH:MM)", group = gG, display = display.none)
inOpenTime = input.string("18:00", " Day-open time (HH:MM)", group = gG, display = display.none)
inGapMin = input.float(0.02, " Skip gaps smaller than N × daily ATR", minval = 0, maxval = 1, step = 0.01, group = gG, display = display.none)
inNdogBack = input.int(3, " NDOG — days back", minval = 1, maxval = 20, group = gG, display = display.none)
inNwogKeep = input.int(5, " NWOG — keep the last N", minval = 1, maxval = 12, group = gG, display = display.none)
inGapRemove = input.bool(false, " Remove a gap once it is filled (otherwise it fades)", group = gG, display = display.none)
gO = "Opens"
inMidOn = input.bool(true, "Midnight open (00:00)", group = gO, tooltip = "ICT's true day open. On regular-hours charts the day's first bar stands in for it.", display = display.none)
in830On = input.bool(true, "08:30 open", group = gO, display = display.none)
in930On = input.bool(true, "09:30 open", group = gO, display = display.none)
inOpenBack = input.int(1, " Opens — days back", minval = 1, maxval = 10, group = gO, display = display.none)
gL = "Previous highs / lows"
inPdOn = input.bool(true, "Previous day high / low (PDH · PDL)", group = gL, display = display.none)
inPwOn = input.bool(true, "Previous week high / low (PWH · PWL)", group = gL, display = display.none)
inPmOn = input.bool(false, "Previous month high / low (PMH · PML)", group = gL, display = display.none)
inP4On = input.bool(false, "Previous 4-hour high / low (P4H · P4L)", group = gL, tooltip = "The intraday HTF pair: the last closed 4-hour bar's high and low, replaced when the next one closes.", display = display.none)
inLvlBack = input.int(3, " Daily levels — days back", minval = 1, maxval = 30, group = gL, display = display.none)
inLvlRemove = input.bool(false, " Remove a level once it is broken (otherwise it fades)", group = gL, display = display.none)
gF = "First-presented FVG (FPFVG)"
inFpOn = input.bool(true, "First FVG after the 09:30 open", group = gF, tooltip = "The first three-bar imbalance printed after the anchor time, on the chart timeframe, drawn with its consequent encroachment. States: fresh → touched (price traded into it) → inverted (a bar closed through its far side).", display = display.none)
inFp830 = input.bool(false, " Also the first FVG after 08:30", group = gF, display = display.none)
inFpMid = input.bool(false, " Also the first FVG after midnight", group = gF, display = display.none)
inFpMin = input.float(0.0, " Skip FVGs smaller than N × ATR (chart)", minval = 0, maxval = 3, step = 0.05, group = gF, display = display.none)
inFpBack = input.int(1, " FPFVG — days back", minval = 1, maxval = 10, group = gF, display = display.none)
gM = "Macro windows"
inMacLon = input.bool(true, "London macros (02:33–03:00 · 04:03–04:30)", group = gM, display = display.none)
inMacAm = input.bool(true, "New York AM macros (08:50–09:10 · 09:50–10:10 · 10:50–11:10)", group = gM, display = display.none)
inMacLunch = input.bool(true, "Lunch macro (11:50–12:10)", group = gM, display = display.none)
inMacPm = input.bool(true, "New York PM macros (13:10–13:40 · 15:15–15:45)", group = gM, display = display.none)
inMacStyle = input.string("Shade + markers", "Draw as", options = ["Shade + markers", "Markers", "Off"], group = gM, tooltip = "Shade = a soft tint across the window's minutes (the classic ICT-macro look); markers = a dotted line at the window's start and end with a tiny tag. The panel always shows the countdown.", display = display.none)
inMacBack = input.int(1, " Macro markers — days back", minval = 1, maxval = 10, group = gM, display = display.none)
gK = "Killzones (session boxes)"
inKzOn = input.bool(true, "Session boxes: Asia · London · New York AM · New York PM", group = gK, tooltip = "Each session's high-low range as a soft box with its name; when the session ends, its high and low become liquidity lines that stay until swept or broken.", display = display.none)
inKzAsia = input.session("2000-0000", " Asia", group = gK, display = display.none)
inKzLon = input.session("0200-0500", " London", group = gK, display = display.none)
inKzAm = input.session("0830-1100", " New York AM", group = gK, display = display.none)
inKzPm = input.session("1330-1600", " New York PM", group = gK, display = display.none)
inKzLines = input.bool(true, " Session high / low lines until swept", group = gK, display = display.none)
inKzBack = input.int(2, " Sessions — days back", minval = 1, maxval = 10, group = gK, display = display.none)
gP = "Dealing range (premium / discount)"
inDrOn = input.bool(true, "Premium / discount between the previous day high and low", group = gP, tooltip = "The previous day's range split at equilibrium: above it price is in premium (rose), below it in discount (teal). Replaced when a new day's levels are born.", display = display.none)
gV = "Style"
inTheme = input.string("Aurora Neon", "Theme", options = ["Aurora Neon", "Royal Gold", "Colorblind-safe"], group = gV, display = display.none)
inProxOn = input.bool(true, "Proximity filter: draw only levels within N × daily ATR of price", group = gV, tooltip = "The anti-clutter rule. Everything is still tracked; only what is near is drawn, and the panel says how many levels are hidden.", display = display.none)
inProxAtr = input.float(2.0, " N × daily ATR", minval = 0.25, maxval = 10, step = 0.25, group = gV, display = display.none)
inLabels = input.string("Short", "Labels", options = ["Full", "Short", "Off"], group = gV, tooltip = "Full = name and price · Short = name only · Off = lines only.", display = display.none)
inLabelGap = input.int(0, " Label anchor (bars right of the last bar; the text runs left)", minval = 0, maxval = 20, group = gV, display = display.none)
inFadeAlpha = input.int(80, " Used levels fade to this transparency", minval = 50, maxval = 95, group = gV, display = display.none)
inMarkers = input.bool(true, "Event marks on the chart (⚡ sweep · ↻ inversion)", group = gV, display = display.none)
inBoxTags = input.bool(true, "Name tags on gap / FVG / session boxes", group = gV, display = display.none)
gD = "Dashboard"
inDash = input.bool(true, "Show the dashboard", group = gD, display = display.none)
inDashPos = input.string("Middle Left", "Position", options = ["Top Right", "Top Left", "Bottom Right", "Bottom Left", "Middle Right", "Middle Left"], group = gD, display = display.none)
inSize = input.string("Medium", "Size", options = ["Small", "Medium", "Large"], group = gD, display = display.none)
inShowRec = input.bool(true, "Show the base rates (record)", group = gD, display = display.none)
gN = "Co-Pilot (narrative)"
inNarr = input.string("Brief", "Narrative depth", options = ["Off", "Brief", "Standard", "Detailed"], group = gN, tooltip = "Brief = one sentence (NOW). Standard adds WHY and WATCH. Detailed adds the base rates and a glossary.", display = display.none)
inNarrPos = input.string("Bottom Left", "Position", options = ["Bottom Left", "Bottom Right", "Top Left", "Top Right", "Middle Left", "Middle Right"], group = gN, display = display.none)
inNarrSize = input.string("Auto", "Size", options = ["Auto", "Small", "Medium", "Large"], group = gN, display = display.none)
inNarrWrap = input.int(64, "Line width (characters)", minval = 30, maxval = 140, group = gN, display = display.none)
gA = "Alerts"
inAlertJson = input.bool(true, "alert() payload as JSON (webhooks)", group = gA, tooltip = "Choose 'Any alert() function call' when creating the alert.", display = display.none)
inNearAtr = input.float(0.15, "Alert when price comes within N × daily ATR of a live level (0 = off)", minval = 0, maxval = 2, step = 0.05, group = gA, tooltip = "Fires once when price arrives near a level that is still live (not filled / broken / inverted) and re-arms after price leaves. The payload names the level.", display = display.none)
// ─────────────────────────────────────────────────────────────────── THEME
type Theme
color bg = #0B0E14
color frame = #B8923A
color accent = #D8B14A
color accentHi = #E8C25A
color up = #2BE7C7
color down = #FF3B5C
color flat = #6B7280
color txt = #D7DCE6
color txtHi = #ECEFF5
themeOf(string name) =>
Theme t = Theme.new()
if name == "Aurora Neon"
t.bg := #0E1118
t.frame := #2C3A5A
t.accent := #7C9CFF
t.accentHi := #A9B8FF
t.up := #2AF0C8
t.down := #FF4D6D
t.flat := #5B6472
t.txt := #E6E9EF
t.txtHi := #F2F4F9
else if name == "Colorblind-safe"
t.bg := #0E1014
t.frame := #3A4150
t.accent := #56B4E9
t.accentHi := #8ED0F5
t.up := #E69F00
t.down := #0072B2
t.flat := #8C92A0
t.txt := #E8EAED
t.txtHi := #FFFFFF
t
Theme th = themeOf(inTheme)
fmt(float x) => str.tostring(x, format.mintick)
color cGapW = th.accentHi // NWOG
color cGapD = th.accent // NDOG
color cOpen = th.txtHi // midnight open
color cOpen2 = th.flat // 08:30 / 09:30 opens
color cHigh = th.down // previous highs (resting sell-side above)
color cLow = th.up // previous lows
color cFvgU = th.up
color cFvgD = th.down
color cMac = th.flat
color cAsia = #F5B84A
color cLon = #4FA3FF
color cAm = th.up
color cPm = #B57BFF
color cPrem = th.down
color cDisc = th.up
sessColor(string n) => n == "Asia" ? cAsia : n == "London" ? cLon : n == "NY AM" ? cAm : cPm
// ─────────────────────────────────────────────────────────────────── CLOCK (all in the input timezone)
int hh = hour(time, inTz)
int mi = minute(time, inTz)
int nowMin = hh * 60 + mi
bool newDay = dayofmonth(time, inTz) != dayofmonth(time[1], inTz) or month(time, inTz) != month(time[1], inTz)
int dow = dayofweek(time, inTz)
int dowPrev = dayofweek(time[1], inTz)
bool afterWeekend = newDay and (dowPrev == dayofweek.friday or dowPrev == dayofweek.saturday)
bool newMonth = month(time, inTz) != month(time[1], inTz)
var int dayNo = 0
if newDay
dayNo += 1
var int pdSerial = 0
var int gapSerial = 0
clockMin(string hm) =>
array<string> parts = str.split(hm, ":")
array.size(parts) == 2 ? int(nz(str.tonumber(array.get(parts, 0)), -1)) * 60 + int(nz(str.tonumber(array.get(parts, 1)), 0)) : -1
crossT(int t) => t >= 0 and nowMin >= t and (newDay or nowMin[1] < t) // the first bar at or after time t in the day
string hhmm = str.tostring(hh, "00") + ":" + str.tostring(mi, "00")
int closeT = clockMin(inCloseTime)
int openT = clockMin(inOpenTime)
if crossT(openT)
gapSerial += 1
bool insideDay = openT > closeT ? (nowMin >= openT or nowMin < closeT) : (nowMin >= openT and nowMin < closeT)
bool x830 = crossT(510)
bool x930 = crossT(570)
bool xClose = crossT(closeT)
bool xOpen = crossT(openT)
// ─────────────────────────────────────────────────────────────────── DATA (all ta.* / security at global scope)
float atr = ta.atr(14)
float atrU = math.max(atr, syminfo.mintick)
dAtrFn() => ta.atr(14)
float dAtr = request.security(syminfo.tickerid, "D", dAtrFn()[1], lookahead = barmerge.lookahead_on)
float dAtrU = math.max(nz(dAtr, atrU * 10), syminfo.mintick)
[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)
[p4h, p4l] = request.security(syminfo.tickerid, "240", [high[1], low[1]], lookahead = barmerge.lookahead_on)
bool p4New = p4h != p4h[1] or p4l != p4l[1]
bool pdNew = pdh != pdh[1] or pdl != pdl[1]
if pdNew
pdSerial += 1
bool pwNew = pwh != pwh[1] or pwl != pwl[1]
bool pmNew = pmh != pmh[1] or pml != pml[1]
bool fvgUp = low > high[2] and (inFpMin == 0 or low - high[2] >= inFpMin * atrU)
bool fvgDn = high < low[2] and (inFpMin == 0 or low[2] - high >= inFpMin * atrU)
// ─────────────────────────────────────────────────────────────────── LEVEL REGISTRY
type Lvl
string kind = "" // NWOG · NDOG · MID · O830 · O930 · PDH · PDL · PWH · PWL · PMH · PML · FPFVG
float px = na // line levels
float top = na // gaps / FVGs
float bot = na
int dir = 0 // gap: +1 = gapped up (open above the close) · FVG: +1 bullish
int bornBar = na
int bornDay = 0
bool touched = false
bool done = false // filled (gap) · broken (level) · inverted (FVG)
bool swept = false // wick through and close back (levels)
int doneBar = na
string anchor = "" // FPFVG: which open it followed
line ln = na
line ce = na
box bx = na
label lb = na
label lb2 = na
bool visible = true
bool near = false
type Stat
int nNdog = 0
int nNdogFilled = 0
int nNwog = 0
int nNwogFilled = 0
int nPdh = 0
int nPdhTaken = 0
int nPdl = 0
int nPdlTaken = 0
int nFp = 0
int nFpTouched = 0
int nFpInverted = 0
type Ev
bool ndog = false
bool nwog = false
bool gapEntered = false
bool ceTouched = false
bool pdhSwept = false
bool pdlSwept = false
bool pwhSwept = false
bool pwlSwept = false
bool fpFormed = false
bool fpTouched = false
bool fpInverted = false
bool macroOpen = false
bool midCross = false
bool near = false
bool kzSwept = false
string nearWhat = ""
string what = ""
var array<Lvl> lv = array.new<Lvl>()
var Stat st = Stat.new()
Ev ev = Ev.new()
var array<label> marks = array.new<label>()
if array.size(marks) > 80
label.delete(array.shift(marks))
isGap(string k) => k == "NDOG" or k == "NWOG"
isFvg(string k) => k == "FPFVG"
isHigh(string k) => k == "PDH" or k == "PWH" or k == "PMH" or k == "P4H" or k == "KZH"
isLow(string k) => k == "PDL" or k == "PWL" or k == "PML" or k == "P4L" or k == "KZL"
kindColor(string k) => k == "NWOG" ? cGapW : k == "NDOG" ? cGapD : k == "MID" ? cOpen : (k == "O830" or k == "O930") ? cOpen2 : isHigh(k) ? cHigh : isLow(k) ? cLow : cFvgU
kindName(string k) => k == "MID" ? "00:00 open" : k == "O830" ? "08:30 open" : k == "O930" ? "09:30 open" : k == "FPFVG" ? "FPFVG" : k
ageOf(Lvl L) => L.kind == "NDOG" ? gapSerial - L.bornDay : (L.kind == "PDH" or L.kind == "PDL") ? pdSerial - L.bornDay : dayNo - L.bornDay
ageTag(Lvl L) => (L.kind == "NDOG" or L.kind == "PDH" or L.kind == "PDL" or L.kind == "MID" or L.kind == "O830" or L.kind == "O930" or isFvg(L.kind)) and ageOf(L) > 0 ? " −" + str.tostring(ageOf(L)) + "d" : ""
lvlRef(Lvl L) => isGap(L.kind) or isFvg(L.kind) ? (L.top + L.bot) / 2 : L.px
lvlName(Lvl L) => L.kind == "KZH" ? L.anchor + " high" : L.kind == "KZL" ? L.anchor + " low" : isFvg(L.kind) ? "FPFVG " + L.anchor : kindName(L.kind)
lvlColor(Lvl L) => isFvg(L.kind) ? (L.dir == 1 ? cFvgU : cFvgD) : (L.kind == "KZH" or L.kind == "KZL") ? sessColor(L.anchor) : kindColor(L.kind)
delLvl(Lvl L) =>
if not na(L.ln)
line.delete(L.ln)
if not na(L.ce)
line.delete(L.ce)
if not na(L.bx)
box.delete(L.bx)
if not na(L.lb)
label.delete(L.lb)
if not na(L.lb2)
label.delete(L.lb2)
addLine(string kind, float px) =>
Lvl L = Lvl.new()
L.kind := kind
L.px := px
L.bornBar := bar_index
L.bornDay := (kind == "PDH" or kind == "PDL") ? pdSerial : dayNo
color c = kindColor(kind)
L.ln := line.new(bar_index, px, bar_index + 1, px, extend = extend.right, color = color.new(c, 20), width = kind == "MID" ? 2 : 1, style = kind == "MID" ? line.style_solid : (kind == "O830" or kind == "O930") ? line.style_dashed : line.style_solid)
L.lb := label.new(bar_index, px, "", style = label.style_label_right, color = color.new(th.bg, 100), textcolor = color.new(c, 10), size = size.tiny)
array.push(lv, L)
L
addBox(string kind, float top, float bot, int dir, string anchor) =>
Lvl L = Lvl.new()
L.kind := kind
L.top := top
L.bot := bot
L.dir := dir
L.anchor := anchor
L.bornBar := bar_index
L.bornDay := kind == "NDOG" ? gapSerial : dayNo
color c = kind == "FPFVG" ? (dir == 1 ? cFvgU : cFvgD) : kindColor(kind)
int x1 = kind == "FPFVG" ? bar_index - 2 : bar_index
L.bx := box.new(x1, top, bar_index + 1, bot, extend = extend.right, border_color = color.new(c, kind == "NWOG" ? 30 : 50), border_width = 1, bgcolor = color.new(c, kind == "NWOG" ? 80 : kind == "NDOG" ? 86 : 82))
L.ce := line.new(x1, (top + bot) / 2, bar_index + 1, (top + bot) / 2, extend = extend.right, color = color.new(c, 40), width = 1, style = line.style_dotted)
L.lb := label.new(bar_index, top, "", style = label.style_label_right, color = color.new(th.bg, 100), textcolor = color.new(c, 10), size = size.tiny)
if inBoxTags
L.lb2 := label.new(x1, top, (kind == "FPFVG" ? "FPFVG " + anchor : kind) + " · " + fmt(top - bot), style = label.style_label_lower_right, color = color.new(th.bg, 100), textcolor = color.new(c, 20), size = size.tiny)
array.push(lv, L)
L
countKind(string kind) =>
int n = 0
if array.size(lv) > 0
for L in lv
if L.kind == kind
n += 1
n
// ─────────────────────────────────────────────────────────────────── BIRTH (confirmed bars; every level is final when it is born)
var float lastCloseAtT = na
var int lastCloseDow = 0
var bool fpArmed930 = false
var bool fpArmed830 = false
var bool fpArmedMid = false
var float midOpenPx = na
var bool ndogOpenToday = false // an NDOG born today, still to be judged at the next open
var bool ndogFilledToday = false
var bool nwogOpen = false
var bool nwogFilledWk = false
var bool pdhTakenToday = false
var bool pdlTakenToday = false
var bool pdhSeenToday = false
var bool fpOpen = false
var bool fpTouchedToday = false
if barstate.isconfirmed
// ── day roll-over: base-rate bookkeeping for yesterday, prune by days back
if newDay
if ndogOpenToday
st.nNdogFilled += ndogFilledToday ? 1 : 0
ndogOpenToday := false
ndogFilledToday := false
if pdhSeenToday
st.nPdhTaken += pdhTakenToday ? 1 : 0
st.nPdlTaken += pdlTakenToday ? 1 : 0
pdhSeenToday := false
pdhTakenToday := false
pdlTakenToday := false
if fpOpen
st.nFpTouched += fpTouchedToday ? 1 : 0
fpOpen := false
fpTouchedToday := false
fpArmed930 := false
fpArmed830 := false
fpArmedMid := inFpOn and inFpMid
if afterWeekend and nwogOpen
st.nNwogFilled += nwogFilledWk ? 1 : 0
nwogOpen := false
nwogFilledWk := false
if array.size(lv) > 0
for i = array.size(lv) - 1 to 0
Lvl L = array.get(lv, i)
int back = L.kind == "NDOG" ? inNdogBack : (L.kind == "MID" or L.kind == "O830" or L.kind == "O930") ? inOpenBack : isFvg(L.kind) ? inFpBack : (L.kind == "NWOG" or L.kind == "PWH" or L.kind == "PWL" or L.kind == "PMH" or L.kind == "PML" or L.kind == "P4H" or L.kind == "P4L") ? 9999 : (L.kind == "KZH" or L.kind == "KZL") ? inKzBack : inLvlBack
if ageOf(L) >= back
delLvl(L)
array.remove(lv, i)
// NWOG: keep the last N
int nw = countKind("NWOG")
if nw > inNwogKeep and array.size(lv) > 0
for i = 0 to array.size(lv) - 1
Lvl L = array.get(lv, i)
if L.kind == "NWOG" and nw > inNwogKeep
delLvl(L)
array.remove(lv, i)
nw -= 1
break
if (pdNew or xOpen) and not newDay and array.size(lv) > 0
for i = array.size(lv) - 1 to 0
Lvl L = array.get(lv, i)
if (L.kind == "NDOG" and ageOf(L) >= inNdogBack) or ((L.kind == "PDH" or L.kind == "PDL") and ageOf(L) >= inLvlBack)
delLvl(L)
array.remove(lv, i)
// ── opens
if newDay and inMidOn
midOpenPx := open
addLine("MID", open)
if x830 and in830On and not newDay
addLine("O830", open)
if x930 and in930On and not newDay
addLine("O930", open)
if x930 and inFpOn
fpArmed930 := true
if x830 and inFpOn and inFp830
fpArmed830 := true
// ── previous highs / lows (born when the period rolls)
if pdNew and inPdOn and not na(pdh)
addLine("PDH", pdh)
addLine("PDL", pdl)
pdhSeenToday := true
st.nPdh += 1
st.nPdl += 1
if pwNew and inPwOn and not na(pwh)
if array.size(lv) > 0
for i = array.size(lv) - 1 to 0
Lvl L = array.get(lv, i)
if L.kind == "PWH" or L.kind == "PWL"
delLvl(L)
array.remove(lv, i)
addLine("PWH", pwh)
addLine("PWL", pwl)
if pmNew and inPmOn and not na(pmh)
if array.size(lv) > 0
for i = array.size(lv) - 1 to 0
Lvl L = array.get(lv, i)
if L.kind == "PMH" or L.kind == "PML"
delLvl(L)
array.remove(lv, i)
addLine("PMH", pmh)
addLine("PML", pml)
if p4New and inP4On and not na(p4h)
if array.size(lv) > 0
for i = array.size(lv) - 1 to 0
Lvl L = array.get(lv, i)
if L.kind == "P4H" or L.kind == "P4L"
delLvl(L)
array.remove(lv, i)
addLine("P4H", p4h)
addLine("P4L", p4l)
// ── opening gaps
if xOpen and not na(lastCloseAtT)
float gapSz = math.abs(open - lastCloseAtT)
if gapSz >= inGapMin * dAtrU
bool isWeek = lastCloseDow == dayofweek.friday or lastCloseDow == dayofweek.saturday
if isWeek and inNwogOn
addBox("NWOG", math.max(open, lastCloseAtT), math.min(open, lastCloseAtT), open > lastCloseAtT ? 1 : -1, "")
st.nNwog += 1
nwogOpen := true
nwogFilledWk := false
ev.nwog := true
else if not isWeek and inNdogOn
addBox("NDOG", math.max(open, lastCloseAtT), math.min(open, lastCloseAtT), open > lastCloseAtT ? 1 : -1, "")
st.nNdog += 1
ndogOpenToday := true
ndogFilledToday := false
ev.ndog := true
if insideDay
lastCloseAtT := close
lastCloseDow := dow
// ── first-presented FVG after an anchor
if (fpArmed930 or fpArmed830 or fpArmedMid) and (fvgUp or fvgDn)
string anc = fpArmed930 ? "09:30" : fpArmed830 ? "08:30" : "00:00"
addBox("FPFVG", fvgUp ? low : low[2], fvgUp ? high[2] : high, fvgUp ? 1 : -1, anc)
st.nFp += 1
fpOpen := true
fpTouchedToday := false
ev.fpFormed := true
if fpArmed930
fpArmed930 := false
else if fpArmed830
fpArmed830 := false
else
fpArmedMid := false
// ── states (every active level, on the closed bar)
if array.size(lv) > 0
for L in lv
if L.done
continue
float refN = lvlRef(L)
float dN = isGap(L.kind) or isFvg(L.kind) ? (close > L.top ? close - L.top : close < L.bot ? L.bot - close : 0.0) : math.abs(close - L.px)
bool isNear = inNearAtr > 0 and dN <= inNearAtr * dAtrU
if isNear and not L.near
L.near := true
ev.near := true
ev.nearWhat := lvlName(L) + " " + fmt(refN)
else if not isNear
L.near := false
if isGap(L.kind)
bool inside = bar_index > L.bornBar and low <= L.top and high >= L.bot
if inside and not L.touched
L.touched := true
ev.gapEntered := true
ev.what := L.kind
float ceP = (L.top + L.bot) / 2
if bar_index > L.bornBar and low <= ceP and high >= ceP and not L.swept
L.swept := true // for gaps: CE touched
ev.ceTouched := true
bool filled = L.dir == 1 ? low <= L.bot : high >= L.top
if filled
L.done := true
L.doneBar := bar_index
if L.kind == "NDOG"
ndogFilledToday := true
else
nwogFilledWk := true
else if isFvg(L.kind)
bool inside = bar_index > L.bornBar and low <= L.top and high >= L.bot
if inside and not L.touched
L.touched := true
fpTouchedToday := true
ev.fpTouched := true
bool inverted = bar_index > L.bornBar and (L.dir == 1 ? close < L.bot : close > L.top)
if inverted
L.done := true
L.doneBar := bar_index
st.nFpInverted += 1
ev.fpInverted := true
if inMarkers
array.push(marks, label.new(bar_index, L.dir == 1 ? L.bot : L.top, "↻", style = L.dir == 1 ? label.style_label_down : label.style_label_up, color = color.new(th.bg, 100), textcolor = color.new(th.flat, 20), size = size.small, tooltip = "FPFVG inverted"))
else if isHigh(L.kind)
if high > L.px and not L.touched
L.touched := true
if close < L.px
L.swept := true
if L.kind == "PDH"
ev.pdhSwept := true
pdhTakenToday := true
if L.kind == "PWH"
ev.pwhSwept := true
if L.kind == "KZH"
ev.kzSwept := true
if inMarkers
array.push(marks, label.new(bar_index, high, "⚡", style = label.style_label_down, color = color.new(th.bg, 100), textcolor = color.new(cHigh, 10), size = size.small, tooltip = lvlName(L) + " swept"))
if close > L.px
L.done := true
L.doneBar := bar_index
if L.kind == "PDH"
pdhTakenToday := true
else if isLow(L.kind)
if low < L.px and not L.touched
L.touched := true
if close > L.px
L.swept := true
if L.kind == "PDL"
ev.pdlSwept := true
pdlTakenToday := true
if L.kind == "PWL"
ev.pwlSwept := true
if L.kind == "KZL"
ev.kzSwept := true
if inMarkers
array.push(marks, label.new(bar_index, low, "⚡", style = label.style_label_up, color = color.new(th.bg, 100), textcolor = color.new(cLow, 10), size = size.small, tooltip = lvlName(L) + " swept"))
if close < L.px
L.done := true
L.doneBar := bar_index
if L.kind == "PDL"
pdlTakenToday := true
else
// opens: crossed = touched (kept drawn; the midnight open is the day's premium / discount line)
if not L.touched and ((close > L.px and close[1] <= L.px) or (close < L.px and close[1] >= L.px))
L.touched := true
if L.kind == "MID"
ev.midCross := true
// removal switches
if inGapRemove or inLvlRemove
for i = array.size(lv) - 1 to 0
Lvl L = array.get(lv, i)
if L.done and ((inGapRemove and isGap(L.kind)) or (inLvlRemove and (isHigh(L.kind) or isLow(L.kind))))
delLvl(L)
array.remove(lv, i)
// ─────────────────────────────────────────────────────────────────── MACRO WINDOWS
// ─────────────────────────────────────────────────────────────────── KILLZONES (session boxes → liquidity lines)
type Kz
string name = ""
bool live = false
float hi = na
float lo = na
int startBar = na
box bx = na
label lb = na
var array<Kz> kz = array.from(Kz.new(name = "Asia"), Kz.new(name = "London"), Kz.new(name = "NY AM"), Kz.new(name = "NY PM"))
bool inA = not na(time(timeframe.period, inKzAsia, inTz))
bool inL = not na(time(timeframe.period, inKzLon, inTz))
bool inN1 = not na(time(timeframe.period, inKzAm, inTz))
bool inN2 = not na(time(timeframe.period, inKzPm, inTz))
array<bool> kzIn = array.from(inA, inL, inN1, inN2)
array<bool> kzWas = array.from(inA[1], inL[1], inN1[1], inN2[1])
var array<box> kzBoxes = array.new<box>()
var array<label> kzTags = array.new<label>()
var array<int> kzDay = array.new<int>()
if barstate.isconfirmed and inKzOn
if newDay and array.size(kzBoxes) > 0
for i = array.size(kzBoxes) - 1 to 0
if dayNo - array.get(kzDay, i) >= inKzBack
box.delete(array.get(kzBoxes, i))
label.delete(array.get(kzTags, i))
array.remove(kzBoxes, i)
array.remove(kzTags, i)
array.remove(kzDay, i)
for i = 0 to 3
Kz K = array.get(kz, i)
bool isIn = array.get(kzIn, i)
bool wasIn = array.get(kzWas, i)
color kc = sessColor(K.name)
if isIn and not wasIn
K.live := true
K.hi := high
K.lo := low
K.startBar := bar_index
K.bx := box.new(bar_index, high, bar_index, low, border_color = color.new(kc, 65), border_width = 1, bgcolor = color.new(kc, 88))
K.lb := label.new(bar_index, high, inBoxTags ? K.name : "", style = label.style_label_lower_right, color = color.new(th.bg, 100), textcolor = color.new(kc, 15), size = size.tiny)
array.push(kzBoxes, K.bx)
array.push(kzTags, K.lb)
array.push(kzDay, dayNo)
else if isIn and K.live
K.hi := math.max(K.hi, high)
K.lo := math.min(K.lo, low)
box.set_top(K.bx, K.hi)
box.set_bottom(K.bx, K.lo)
box.set_right(K.bx, bar_index)
label.set_y(K.lb, K.hi)
else if not isIn and wasIn and K.live
K.live := false
if inKzLines and not na(K.hi)
Lvl H = addLine("KZH", K.hi)
H.anchor := K.name
Lvl Lo = addLine("KZL", K.lo)
Lo.anchor := K.name
// ─────────────────────────────────────────────────────────────────── DEALING RANGE (premium / discount between PDH and PDL)
var box drPrem = na
var box drDisc = na
var line drEq = na
var label drL1 = na
var label drL2 = na
var float drEqPx = na
if barstate.isconfirmed and inDrOn and pdNew and not na(pdh) and pdh > pdl
if not na(drPrem)
box.delete(drPrem)
box.delete(drDisc)
line.delete(drEq)
label.delete(drL1)
label.delete(drL2)
drEqPx := (pdh + pdl) / 2
drPrem := box.new(bar_index, pdh, bar_index + 1, drEqPx, extend = extend.right, border_color = color.new(cPrem, 100), bgcolor = color.new(cPrem, 93))
drDisc := box.new(bar_index, drEqPx, bar_index + 1, pdl, extend = extend.right, border_color = color.new(cDisc, 100), bgcolor = color.new(cDisc, 93))
drEq := line.new(bar_index, drEqPx, bar_index + 1, drEqPx, extend = extend.right, color = color.new(th.flat, 45), width = 1, style = line.style_dotted)
drL1 := label.new(bar_index, pdh, inBoxTags ? "premium" : "", style = label.style_label_lower_right, color = color.new(th.bg, 100), textcolor = color.new(cPrem, 40), size = size.tiny)
drL2 := label.new(bar_index, pdl, inBoxTags ? "discount" : "", style = label.style_label_upper_right, color = color.new(th.bg, 100), textcolor = color.new(cDisc, 40), size = size.tiny)
string drTxt = na(drEqPx) ? "—" : (close > drEqPx ? "premium" : close < drEqPx ? "discount" : "at equilibrium") + " · EQ " + fmt(drEqPx) + " · " + str.tostring(math.abs(close - drEqPx) / math.max(pdh - pdl, syminfo.mintick) * 200, "0") + " % of the half-range away"
var array<int> macS = array.from(153, 243, 530, 590, 650, 710, 790, 915)
var array<int> macE = array.from(180, 270, 550, 610, 670, 730, 820, 945)
var array<string> macN = array.from("London 02:33–03:00", "London 04:03–04:30", "NY AM 08:50–09:10", "NY AM 09:50–10:10", "NY AM 10:50–11:10", "Lunch 11:50–12:10", "NY PM 13:10–13:40", "NY PM 15:15–15:45")
macOn(int i) => i < 2 ? inMacLon : i < 5 ? inMacAm : i < 6 ? inMacLunch : inMacPm
bool anyMac = inMacLon or inMacAm or inMacLunch or inMacPm
int macIdx = -1 // window in progress
int macNext = -1 // next window today
bool macStartNow = false
bool macEndNow = false
int prevMin = nz(nowMin[1], -1)
for i = 0 to 7
if macOn(i)
int s0 = array.get(macS, i)
int e0 = array.get(macE, i)
if nowMin >= s0 and nowMin < e0 and macIdx < 0
macIdx := i
if s0 > nowMin and macNext < 0
macNext := i
if nowMin >= s0 and (newDay or prevMin < s0) and nowMin < e0
macStartNow := true
if nowMin >= e0 and (newDay or prevMin < e0)
macEndNow := true
if macStartNow
ev.macroOpen := true
var array<line> macLines = array.new<line>()
var array<int> macLineDay = array.new<int>()
var array<label> macTags = array.new<label>()
var array<int> macTagDay = array.new<int>()
bgcolor(inMacStyle == "Shade + markers" and macIdx >= 0 ? color.new(cMac, 92) : na, title = "Macro window")
if barstate.isconfirmed and inMacStyle != "Off"
if newDay and array.size(macLines) > 0
for i = array.size(macLines) - 1 to 0
if dayNo - array.get(macLineDay, i) >= inMacBack
line.delete(array.get(macLines, i))
array.remove(macLines, i)
array.remove(macLineDay, i)
if newDay and array.size(macTags) > 0
for i = array.size(macTags) - 1 to 0
if dayNo - array.get(macTagDay, i) >= inMacBack
label.delete(array.get(macTags, i))
array.remove(macTags, i)
array.remove(macTagDay, i)
if macStartNow
array.push(macLines, line.new(bar_index, low, bar_index, high, extend = extend.both, color = color.new(cMac, 65), width = 1, style = line.style_dotted))
array.push(macLineDay, dayNo)
array.push(macTagDay, dayNo)
array.push(macTags, label.new(bar_index, high, "macro " + str.substring(array.get(macN, macIdx >= 0 ? macIdx : 0), str.length(array.get(macN, macIdx >= 0 ? macIdx : 0)) - 11, str.length(array.get(macN, macIdx >= 0 ? macIdx : 0))), style = label.style_label_down, color = color.new(th.bg, 100), textcolor = color.new(cMac, 20), size = size.tiny, yloc = yloc.abovebar))
if macEndNow
array.push(macLines, line.new(bar_index, low, bar_index, high, extend = extend.both, color = color.new(cMac, 82), width = 1, style = line.style_dotted))
array.push(macLineDay, dayNo)
string macTxt = macIdx >= 0 ? array.get(macN, macIdx) + " · in progress · " + str.tostring(array.get(macE, macIdx) - nowMin) + " m left" : macNext >= 0 ? array.get(macN, macNext) + " · opens in " + str.tostring(array.get(macS, macNext) - nowMin) + " m" : anyMac ? "no more macros today" : "macros off"
// ─────────────────────────────────────────────────────────────────── DISPLAY (last bar): proximity, labels, fading, nearest levels
float band = inProxOn ? inProxAtr * dAtrU : 1e12
int hiddenN = 0
int activeN = 0
// nearest levels above / below (three each)
var array<string> nearUpTxt = array.new<string>()
var array<string> nearDnTxt = array.new<string>()
var array<float> nearUpD = array.new<float>()
var array<float> nearDnD = array.new<float>()
stateWord(Lvl L) =>
isGap(L.kind) ? (L.done ? "filled" : L.swept ? "CE touched" : L.touched ? "entered" : "open") : isFvg(L.kind) ? (L.done ? "inverted" : L.touched ? "touched" : "fresh") : (isHigh(L.kind) or isLow(L.kind)) ? (L.done ? "broken" : L.swept ? "swept" : L.touched ? "tested" : "resting") : (L.touched ? "crossed" : "untested")
if barstate.islast
array.clear(nearUpTxt)
array.clear(nearDnTxt)
array.clear(nearUpD)
array.clear(nearDnD)
if array.size(lv) > 0
// sort indices by distance for label staggering
int stagger = 0
float lastLabelPx = na
for L in lv
activeN += 1
float ref = lvlRef(L)
float dist = isGap(L.kind) or isFvg(L.kind) ? (close > L.top ? close - L.top : close < L.bot ? L.bot - close : 0.0) : math.abs(close - L.px)
bool vis = dist <= band
L.visible := vis
if not vis
hiddenN += 1
color c = lvlColor(L)
int a = vis ? (L.done ? inFadeAlpha : (L.kind == "NWOG" ? 20 : L.kind == "MID" ? 15 : 25)) : 100
int aFill = vis ? (L.done ? math.min(97, inFadeAlpha + 12) : (L.kind == "NWOG" ? 80 : isFvg(L.kind) ? 82 : 86)) : 100
if not na(L.lb2)
label.set_textcolor(L.lb2, color.new(c, vis ? (L.done ? math.min(92, inFadeAlpha + 10) : 20) : 100))
label.set_y(L.lb2, vis ? L.top : close)
// park hidden objects on the current price so the auto-scale ignores them; snap back when visible
float yLine = vis ? L.px : close
float yTop = vis ? L.top : close
float yBot = vis ? L.bot : close
int endX = L.done and not na(L.doneBar) ? L.doneBar : bar_index + 1
if not na(L.ln)
line.set_color(L.ln, color.new(c, a))
line.set_style(L.ln, L.done ? line.style_dashed : (L.kind == "O830" or L.kind == "O930") ? line.style_dashed : line.style_solid)
line.set_y1(L.ln, yLine)
line.set_y2(L.ln, yLine)
line.set_extend(L.ln, L.done ? extend.none : extend.right)
line.set_x2(L.ln, endX)
if not na(L.bx)
box.set_bgcolor(L.bx, color.new(c, aFill))
box.set_border_color(L.bx, color.new(c, vis ? (L.done ? inFadeAlpha : 55) : 100))
box.set_top(L.bx, yTop)
box.set_bottom(L.bx, yBot)
box.set_extend(L.bx, L.done ? extend.none : extend.right)
box.set_right(L.bx, endX)
if not na(L.ce)
line.set_color(L.ce, color.new(c, vis ? (L.done ? math.min(97, inFadeAlpha + 8) : 45) : 100))
line.set_y1(L.ce, (yTop + yBot) / 2)
line.set_y2(L.ce, (yTop + yBot) / 2)
line.set_extend(L.ce, L.done ? extend.none : extend.right)
line.set_x2(L.ce, endX)
if not na(L.lb)
string nm = lvlName(L) + ageTag(L)
string sw = stateWord(L)
string txt = inLabels == "Off" or not vis ? "" : inLabels == "Full" ? nm + " · " + fmt(ref) + (sw == "resting" or sw == "open" or sw == "fresh" or sw == "untested" ? "" : " · " + sw) : nm + (L.done or L.swept ? " · " + sw : "")
label.set_text(L.lb, txt)
label.set_size(L.lb, L.done ? size.tiny : size.small)
label.set_textcolor(L.lb, color.new(c, vis ? (L.done ? math.min(90, inFadeAlpha) : 10) : 100))
label.set_y(L.lb, vis ? (isGap(L.kind) or isFvg(L.kind) ? L.top : L.px) : close)
label.set_x(L.lb, L.done and not na(L.doneBar) ? L.doneBar : bar_index + inLabelGap)
// nearest lists (active, not done)
if not L.done
string row = lvlName(L) + ageTag(L) + " " + fmt(ref) + " · " + (dist == 0 ? "inside" : str.tostring(dist, format.mintick) + " (" + str.tostring(dist / dAtrU, "0.00") + " ATR)") + (L.touched ? " · " + stateWord(L) : "")
if ref > close
array.push(nearUpTxt, row)
array.push(nearUpD, dist)
else if ref < close
array.push(nearDnTxt, row)
array.push(nearDnD, dist)
else
array.push(nearUpTxt, row)
array.push(nearUpD, 0.0)
// stagger the labels of live levels that share (almost) the same price
if array.size(lv) > 1
array<float> ly = array.new<float>()
array<int> li = array.new<int>()
for i = 0 to array.size(lv) - 1
Lvl L = array.get(lv, i)
if L.visible and not L.done and not na(L.lb)
array.push(ly, isGap(L.kind) or isFvg(L.kind) ? L.top : L.px)
array.push(li, i)
if array.size(ly) > 1
for i = 0 to array.size(ly) - 2
for j = i + 1 to array.size(ly) - 1
if array.get(ly, j) < array.get(ly, i)
float ty = array.get(ly, i)
array.set(ly, i, array.get(ly, j))
array.set(ly, j, ty)
int ti = array.get(li, i)
array.set(li, i, array.get(li, j))
array.set(li, j, ti)
int k = 0
for i = 1 to array.size(ly) - 1
k := array.get(ly, i) - array.get(ly, i - 1) < 0.05 * dAtrU ? k + 1 : 0
if k > 0
Lvl L = array.get(lv, array.get(li, i))
label.set_x(L.lb, bar_index + inLabelGap - k * 12)
// sort the nearest lists by distance (selection sort, tiny arrays)
if array.size(nearUpD) > 1
for i = 0 to array.size(nearUpD) - 2
for j = i + 1 to array.size(nearUpD) - 1
if array.get(nearUpD, j) < array.get(nearUpD, i)
float td = array.get(nearUpD, i)
array.set(nearUpD, i, array.get(nearUpD, j))
array.set(nearUpD, j, td)
string tt = array.get(nearUpTxt, i)
array.set(nearUpTxt, i, array.get(nearUpTxt, j))
array.set(nearUpTxt, j, tt)
if array.size(nearDnD) > 1
for i = 0 to array.size(nearDnD) - 2
for j = i + 1 to array.size(nearDnD) - 1
if array.get(nearDnD, j) < array.get(nearDnD, i)
float td = array.get(nearDnD, i)
array.set(nearDnD, i, array.get(nearDnD, j))
array.set(nearDnD, j, td)
string tt = array.get(nearDnTxt, i)
array.set(nearDnTxt, i, array.get(nearDnTxt, j))
array.set(nearDnTxt, j, tt)
// today's objects for the panel
Lvl todayNdog = na
Lvl todayNwog = na
Lvl todayFp = na
Lvl todayMid = na
if array.size(lv) > 0
for L in lv
if L.kind == "NDOG"
todayNdog := L
if L.kind == "NWOG"
todayNwog := L
if L.kind == "FPFVG"
todayFp := L
if L.kind == "MID"
todayMid := L
gapTxt(Lvl L) => na(L) ? "none" : fmt(L.bot) + " – " + fmt(L.top) + " · " + str.tostring((L.top - L.bot) / dAtrU, "0.00") + " ATR · " + (L.dir == 1 ? "gapped up" : "gapped down") + " · " + stateWord(L)
string fpTxt = na(todayFp) ? (inFpOn ? (fpArmed930 or fpArmed830 or fpArmedMid ? "waiting for the first FVG" : "none yet today") : "off") : (todayFp.dir == 1 ? "bullish " : "bearish ") + fmt(todayFp.bot) + " – " + fmt(todayFp.top) + " · after " + todayFp.anchor + " · " + stateWord(todayFp)
string midTxt = na(todayMid) ? "—" : fmt(todayMid.px) + (close > todayMid.px ? " · price above (premium side)" : close < todayMid.px ? " · price below (discount side)" : " · at the open")
string sitTxt = "nearest above: " + (array.size(nearUpTxt) > 0 ? array.get(nearUpTxt, 0) : "nothing tracked") + " · nearest below: " + (array.size(nearDnTxt) > 0 ? array.get(nearDnTxt, 0) : "nothing tracked")
// ─────────────────────────────────────────────────────────────────── DASHBOARD
posOf(string p) =>
p == "Top Left" ? position.top_left : p == "Bottom Right" ? position.bottom_right : p == "Bottom Left" ? position.bottom_left : p == "Middle Right" ? position.middle_right : p == "Middle Left" ? position.middle_left : position.top_right
szLead() => inSize == "Small" ? size.small : inSize == "Large" ? size.large : size.normal
szBody() => inSize == "Small" ? size.tiny : inSize == "Large" ? size.normal : size.small
szSub() => inSize == "Small" ? size.tiny : inSize == "Large" ? size.small : size.tiny
row2(table tb, int r, string k, string v, color vc) =>
color stripe = r % 2 == 1 ? color.new(th.accent, 94) : th.bg
table.cell(tb, 0, r, k, text_color = th.flat, text_size = szBody(), text_halign = text.align_left, bgcolor = stripe)
table.cell(tb, 1, r, v, text_color = vc, text_size = szBody(), text_halign = text.align_left, text_font_family = font.family_monospace, bgcolor = stripe)
section(table tb, int r, string title) =>
table.cell(tb, 0, r, title, text_color = th.accent, text_size = szSub(), text_halign = text.align_left, bgcolor = color.new(th.accent, 90))
table.cell(tb, 1, r, "", bgcolor = color.new(th.accent, 90))
table.merge_cells(tb, 0, r, 1, r)
pct(int a, int b) => b == 0 ? "—" : str.tostring(math.round(a / b * 100)) + " % (" + str.tostring(a) + " of " + str.tostring(b) + ")"
var table dash = table.new(posOf(inDashPos), 2, 24, bgcolor = th.bg, frame_color = th.frame, frame_width = 1, border_width = 0)
if barstate.islast and inDash
table.clear(dash, 0, 0, 1, 23)
int r = 0
table.cell(dash, 0, r, "ICT LEVELS DESK", text_color = th.accentHi, text_size = szLead(), text_halign = text.align_left, bgcolor = th.bg, tooltip = "Opening gaps, opens, previous highs / lows, the first-presented FVG and the macro windows — every level with its state, only the near ones drawn. Context, not signals.")
table.cell(dash, 1, r, syminfo.ticker + " · " + timeframe.period + " · " + hhmm + " " + (inTz == "America/New_York" ? "NY" : inTz), text_color = th.accent, text_size = szSub(), text_halign = text.align_right, bgcolor = th.bg)
r += 1
table.cell(dash, 0, r, "● " + fmt(close), text_color = th.txtHi, text_size = szLead(), text_halign = text.align_left, bgcolor = th.bg)
table.cell(dash, 1, r, str.tostring(activeN) + " levels tracked · " + str.tostring(hiddenN) + " hidden beyond " + str.tostring(inProxAtr, "0.0") + " × daily ATR", text_color = th.flat, text_size = szSub(), text_halign = text.align_right, bgcolor = th.bg)
r += 1
section(dash, r, "NEAREST ABOVE")
r += 1
if array.size(nearUpTxt) == 0
row2(dash, r, "—", "nothing tracked above price", th.flat)
r += 1
else
for i = 0 to math.min(2, array.size(nearUpTxt) - 1)
row2(dash, r, "▲ " + str.tostring(i + 1), array.get(nearUpTxt, i), th.txt)
r += 1
section(dash, r, "NEAREST BELOW")
r += 1
if array.size(nearDnTxt) == 0
row2(dash, r, "—", "nothing tracked below price", th.flat)
r += 1
else
for i = 0 to math.min(2, array.size(nearDnTxt) - 1)
row2(dash, r, "▼ " + str.tostring(i + 1), array.get(nearDnTxt, i), th.txt)
r += 1
section(dash, r, "TODAY")
r += 1
if inMidOn
row2(dash, r, "00:00 open", midTxt, th.txt)
r += 1
if inDrOn
row2(dash, r, "Dealing range", drTxt, na(drEqPx) ? th.flat : close > drEqPx ? cPrem : cDisc)
r += 1
if inNdogOn
row2(dash, r, "NDOG", gapTxt(todayNdog), na(todayNdog) ? th.flat : todayNdog.done ? th.flat : th.txt)
r += 1
if inNwogOn
row2(dash, r, "NWOG", gapTxt(todayNwog), na(todayNwog) ? th.flat : todayNwog.done ? th.flat : th.txt)
r += 1
if inFpOn
row2(dash, r, "FPFVG", fpTxt, na(todayFp) ? th.flat : todayFp.done ? th.flat : (todayFp.dir == 1 ? th.up : th.down))
r += 1
row2(dash, r, "Macro", macTxt, macIdx >= 0 ? th.accentHi : th.txt)
r += 1
if inShowRec
section(dash, r, "BASE RATES ON THIS CHART · DESCRIPTIVE, NOT PREDICTIVE")
r += 1
row2(dash, r, "NDOG filled same day", pct(st.nNdogFilled, st.nNdog), th.txt)
r += 1
row2(dash, r, "NWOG filled same week", pct(st.nNwogFilled, st.nNwog), th.txt)
r += 1
row2(dash, r, "PDH · PDL taken", pct(st.nPdhTaken, st.nPdh) + " · " + pct(st.nPdlTaken, st.nPdl), th.txt)
r += 1
row2(dash, r, "FPFVG revisited · inverted", pct(st.nFpTouched, st.nFp) + " · " + str.tostring(st.nFpInverted), th.txt)
r += 1
string mods = (inNdogOn ? "NDOG " + str.tostring(inNdogBack) + "d · " : "") + (inNwogOn ? "NWOG ×" + str.tostring(inNwogKeep) + " · " : "") + (inMidOn or in830On or in930On ? "opens " + str.tostring(inOpenBack) + "d · " : "") + (inPdOn ? "PD " : "") + (inPwOn ? "PW " : "") + (inPmOn ? "PM " : "") + (inPdOn or inPwOn or inPmOn ? str.tostring(inLvlBack) + "d · " : "") + (inFpOn ? "FPFVG " + str.tostring(inFpBack) + "d" : "")
table.cell(dash, 0, r, mods, text_color = th.flat, text_size = szSub(), text_halign = text.align_left, bgcolor = th.bg)
table.cell(dash, 1, r, "gaps " + inCloseTime + " → " + inOpenTime + " · " + (inProxOn ? "proximity on" : "proximity off"), text_color = th.flat, text_size = szSub(), text_halign = text.align_right, bgcolor = th.bg)
// ─────────────────────────────────────────────────────────────────── CO-PILOT
wrap(string s, int width) =>
array<string> words = str.split(s, " ")
string out = ""
string line = ""
for w in words
if str.length(line) + str.length(w) + 1 > width and line != ""
out := out + line + "\n"
line := w
else
line := line == "" ? w : line + " " + w
out + line
narrNow() =>
"price " + fmt(close) + " · " + sitTxt + (na(todayNdog) ? "" : "; today's NDOG " + fmt(todayNdog.bot) + "–" + fmt(todayNdog.top) + " is " + stateWord(todayNdog)) + (na(todayFp) ? "" : "; the " + todayFp.anchor + " first-presented FVG is " + (todayFp.dir == 1 ? "bullish" : "bearish") + " and " + stateWord(todayFp)) + "; " + macTxt
narrWhy() =>
"The ICT reading of these levels: opening gaps (NDOG / NWOG) are inefficiencies the market tends to revisit — their midline is the consequent encroachment; the midnight open frames the day (above it is premium, below it discount), the 08:30 and 09:30 opens do the same for the sessions; previous highs and lows are resting liquidity that gets swept before reversals; the first FVG after the open is the day's first imbalance; macro windows are the minutes the algorithm is said to seek liquidity. This desk shows where they are and what state each one is in — it does not say what price will do."
narrWatch() =>
(array.size(nearUpTxt) > 0 ? "Above: " + array.get(nearUpTxt, 0) + ". " : "") + (array.size(nearDnTxt) > 0 ? "Below: " + array.get(nearDnTxt, 0) + ". " : "") + (macIdx >= 0 ? "A macro is in progress — sweeps of the nearest level here are the classic read. " : macNext >= 0 ? "The next macro is " + array.get(macN, macNext) + ". " : "") + "Levels hidden by the proximity filter come back when price gets within " + str.tostring(inProxAtr, "0.0") + " × the daily ATR."
narrRates() =>
st.nNdog + st.nPdh < 10 ? "Too few days on this chart to say anything yet." : "On this chart the NDOG filled the same day " + pct(st.nNdogFilled, st.nNdog) + ", the NWOG the same week " + pct(st.nNwogFilled, st.nNwog) + ", PDH was taken " + pct(st.nPdhTaken, st.nPdh) + " of days and PDL " + pct(st.nPdlTaken, st.nPdl) + ", the first-presented FVG was revisited " + pct(st.nFpTouched, st.nFp) + ". Descriptive base rates of the past on THIS chart — not a forecast."
narrPosOf(string p) =>
p == "Top Left" ? position.top_left : p == "Top Right" ? position.top_right : p == "Bottom Right" ? position.bottom_right : p == "Middle Left" ? position.middle_left : p == "Middle Right" ? position.middle_right : position.bottom_left
narrBodySz() => inNarrSize == "Small" ? size.small : inNarrSize == "Medium" ? size.normal : inNarrSize == "Large" ? size.large : szBody()
narrHeadSz() => inNarrSize == "Small" ? size.normal : inNarrSize == "Medium" ? size.large : inNarrSize == "Large" ? size.large : szLead()
var table narrT = na
if barstate.islast
if not na(narrT)
table.delete(narrT)
narrT := na
if inNarr != "Off"
array<string> labs = array.new<string>()
array<string> vals = array.new<string>()
array.push(labs, "NOW")
array.push(vals, wrap(narrNow() + " (context, not a trade instruction)", inNarrWrap))
bool deep = inNarr == "Standard" or inNarr == "Detailed"
if deep
array.push(labs, "WHY")
array.push(vals, wrap(narrWhy(), inNarrWrap))
array.push(labs, "WATCH")
array.push(vals, wrap(narrWatch(), inNarrWrap))
if inNarr == "Detailed"
array.push(labs, "BASE RATES")
array.push(vals, wrap(narrRates(), inNarrWrap))
array.push(labs, "GLOSSARY")
array.push(vals, wrap("NDOG / NWOG = the new-day / new-week opening gap between the day-close and the day-open prices. CE = consequent encroachment, the midline of a gap or FVG. FPFVG = the first fair value gap presented after an open. Swept = the wick went through a high or low and the bar closed back. Broken = a close beyond it. Inverted = a close through the far side of an FVG.", inNarrWrap))
int nr = array.size(labs)
bool atBottom = inNarrPos == "Bottom Left" or inNarrPos == "Bottom Right"
int rows = nr + 2 + (atBottom ? 1 : 0)
narrT := table.new(narrPosOf(inNarrPos), 2, rows, bgcolor = th.bg, frame_color = th.frame, frame_width = 1, border_color = color.new(th.frame, 70), border_width = 1)
table.cell(narrT, 0, 0, "◆ CO-PILOT", text_color = th.accentHi, bgcolor = color.new(th.accent, 88), text_size = narrHeadSz(), text_halign = text.align_left)
table.cell(narrT, 1, 0, (macIdx >= 0 ? "MACRO IN PROGRESS" : "LEVELS") + " · " + str.upper(inNarr), text_color = th.accent, bgcolor = color.new(th.accent, 88), text_size = narrBodySz(), text_halign = text.align_right)
for i = 0 to nr - 1
table.cell(narrT, 0, i + 1, array.get(labs, i), text_color = th.accent, bgcolor = th.bg, text_size = narrBodySz(), text_halign = text.align_left, text_valign = text.align_top)
table.cell(narrT, 1, i + 1, array.get(vals, i), text_color = th.txt, bgcolor = th.bg, text_size = narrBodySz(), text_halign = text.align_left, text_valign = text.align_top)
table.cell(narrT, 0, nr + 1, "", bgcolor = th.bg)
table.cell(narrT, 1, nr + 1, "Honest: levels are where price has been, not where it must go. The base rates describe this chart's past.", text_color = th.flat, bgcolor = th.bg, text_size = narrBodySz(), text_halign = text.align_left)
if atBottom
table.cell(narrT, 0, nr + 2, " ", bgcolor = th.bg)
table.cell(narrT, 1, nr + 2, " ", bgcolor = th.bg)
// ─────────────────────────────────────────────────────────────────── ALERTS (closed bars by construction)
payload(string evName, string what) =>
string js = '{"src":"ICTLevelsDesk","symbol":"' + syminfo.ticker + '","tf":"' + timeframe.period + '","event":"' + evName + '","what":"' + what + '","price":' + fmt(close) + ',"nearestAbove":"' + (array.size(nearUpTxt) > 0 ? array.get(nearUpTxt, 0) : "") + '","nearestBelow":"' + (array.size(nearDnTxt) > 0 ? array.get(nearDnTxt, 0) : "") + '","macro":"' + macTxt + '"}'
string tx = "ICT Levels Desk · " + syminfo.ticker + " " + timeframe.period + " · " + evName + (what != "" ? " · " + what : "") + " · " + fmt(close)
inAlertJson ? js : tx
alertcondition(ev.ndog, "NDOG formed", "ICT Levels Desk: a new-day opening gap formed")
alertcondition(ev.nwog, "NWOG formed", "ICT Levels Desk: a new-week opening gap formed")
alertcondition(ev.gapEntered, "Price entered a gap", "ICT Levels Desk: price traded into an opening gap")
alertcondition(ev.ceTouched, "Gap CE touched", "ICT Levels Desk: price touched a gap's consequent encroachment")
alertcondition(ev.pdhSwept, "PDH swept", "ICT Levels Desk: the previous day high was swept (wick through, close back)")
alertcondition(ev.pdlSwept, "PDL swept", "ICT Levels Desk: the previous day low was swept (wick through, close back)")
alertcondition(ev.pwhSwept, "PWH swept", "ICT Levels Desk: the previous week high was swept")
alertcondition(ev.pwlSwept, "PWL swept", "ICT Levels Desk: the previous week low was swept")
alertcondition(ev.fpFormed, "First-presented FVG formed", "ICT Levels Desk: the first FVG after the open printed")
alertcondition(ev.fpTouched, "First-presented FVG touched", "ICT Levels Desk: price traded back into the first-presented FVG")
alertcondition(ev.fpInverted, "First-presented FVG inverted", "ICT Levels Desk: a bar closed through the first-presented FVG")
alertcondition(ev.macroOpen, "Macro window opening", "ICT Levels Desk: a macro window just opened")
alertcondition(ev.midCross, "Midnight open crossed", "ICT Levels Desk: price closed across the midnight open")
alertcondition(ev.near, "Price near a level", "ICT Levels Desk: price came within the set distance of a live level")
alertcondition(ev.kzSwept, "Session high / low swept", "ICT Levels Desk: a killzone session high or low was swept")
if ev.ndog
alert(payload("NDOG", ""), alert.freq_once_per_bar_close)
if ev.nwog
alert(payload("NWOG", ""), alert.freq_once_per_bar_close)
if ev.gapEntered
alert(payload("GAP_ENTERED", ev.what), alert.freq_once_per_bar_close)
if ev.pdhSwept or ev.pdlSwept or ev.pwhSwept or ev.pwlSwept
alert(payload("SWEEP", ev.pdhSwept ? "PDH" : ev.pdlSwept ? "PDL" : ev.pwhSwept ? "PWH" : "PWL"), alert.freq_once_per_bar_close)
if ev.fpFormed
alert(payload("FPFVG_FORMED", ""), alert.freq_once_per_bar_close)
if ev.fpInverted
alert(payload("FPFVG_INVERTED", ""), alert.freq_once_per_bar_close)
if ev.macroOpen
alert(payload("MACRO_OPEN", macTxt), alert.freq_once_per_bar_close)
if ev.near
alert(payload("NEAR_LEVEL", ev.nearWhat), alert.freq_once_per_bar_close)