//@version=6 indicator("GEX Levels — Paste-In", shorttitle = "GEX Levels", overlay = true, max_lines_count = 100, max_labels_count = 200) // ════════════════════════════════════════════════════════════════════════════ // HOW TO USE // 1) Run gex_levels.py (or ask Claude) each morning → get a paste string. // 2) Paste into "Paste levels". One line per symbol, prefixed with the ticker: // SPX:CW=7700;PW=7650;GF=7684;CW0=7700;PW0=7650 // MU:CW=1100;CW2=1150;PW=1000;PW2=980;GF=977 // Keys: CW, CW2, PW, PW2, GF, CW0 (0DTE call wall), PW0 (0DTE put wall) // Optional: CONF=HIGH|GOOD|MED|LOW (confidence, shown on every label) // CWM, CW2M, PWM, PW2M, CW0M, PW0M = wall strength in $M per 1% move // A line with no "TICKER:" prefix is used for any chart without its own line. // 3) The script picks the line matching the chart. ES/MES/SPY/XSP use the SPX // line, NQ/MNQ/QQQ use the NDX line, and SPX strikes are converted to chart // prices automatically (live ES–SPX basis for futures, SPY/SPX ratio for ETFs). // ════════════════════════════════════════════════════════════════════════════ // ── Inputs: levels ────────────────────────────────────────────────────────── string G_LV = "Levels" string levelsTxt = input.text_area("SPX:CW=7700;PW=7650;GF=7684;CW0=7700;CONF=HIGH\nMU:CW=1100;CW2=1150;PW=1000;PW2=1050;GF=989.87;CONF=GOOD;CWM=105;PWM=49", "Paste levels", tooltip = "One line per symbol: TICKER:CW=..;PW=..;GF=..;CW0=..;PW0=..;CW2=..;PW2=..;CONF=HIGH;CWM=..;PWM=.. Use ; between keys. No thousands separators.", group = G_LV) string blockOverride = input.string("", "Force line (blank = auto)", tooltip = "Type a ticker prefix (e.g. SPX) to force which pasted line this chart uses.", group = G_LV) // ── Inputs: conversion ────────────────────────────────────────────────────── string G_CV = "Conversion (levels from another symbol)" string convMode = input.string("Auto", "Mode", options = ["Auto", "None", "Spread (futures)", "Ratio (ETF)"], tooltip = "Auto: futures → add live basis, ETFs → multiply by live ratio, same symbol → none.", group = G_CV) string srcSym = input.symbol("SP:SPX", "Levels underlying", tooltip = "Index the pasted strikes belong to. Use NASDAQ:NDX for NQ/QQQ.", group = G_CV) float manualAdj = input.float(0.0, "Manual basis / ratio (0 = live)", tooltip = "Spread mode: points to add (e.g. 62.56). Ratio mode: multiplier (e.g. 0.0998). 0 uses the live calculation.", group = G_CV) // ── Inputs: display ───────────────────────────────────────────────────────── string G_DS = "Display" bool showCW = input.bool(true, "Call Wall", inline = "a", group = G_DS) color cCW = input.color(#FF5252, "", inline = "a", group = G_DS) bool showPW = input.bool(true, "Put Wall", inline = "b", group = G_DS) color cPW = input.color(#00E676, "", inline = "b", group = G_DS) bool showGF = input.bool(true, "Gamma Flip", inline = "c", group = G_DS) color cGF = input.color(#FFD600, "", inline = "c", group = G_DS) bool show0 = input.bool(true, "0DTE walls", inline = "d", group = G_DS) color c0CW = input.color(#FF8A80, "", inline = "d", group = G_DS) color c0PW = input.color(#69F0AE, "", inline = "d", group = G_DS) bool show2 = input.bool(true, "2nd walls", inline = "e", group = G_DS) color c2CW = input.color(#EF9A9A, "", inline = "e", group = G_DS) color c2PW = input.color(#A5D6A7, "", inline = "e", group = G_DS) int lineW = input.int(2, "Line width", minval = 1, maxval = 4, group = G_DS) string extOpt = input.string("Both", "Extend lines", options = ["Right", "Both"], group = G_DS) int lblOff = input.int(8, "Label offset (bars)", minval = 0, maxval = 100, group = G_DS) bool showConv = input.bool(true, "Show converted price in labels", group = G_DS) bool showConf = input.bool(true, "Show confidence in labels", group = G_DS) bool showStr = input.bool(true, "Show wall strength ($M) in labels", group = G_DS) bool fadeLow = input.bool(true, "Fade LOW-confidence levels", group = G_DS) bool showBg = input.bool(true, "Regime background (above / below flip)", group = G_DS) bool showTbl = input.bool(true, "Dashboard", group = G_DS) string tblPos = input.string("Top Right", "Dashboard position", options = ["Top Right", "Bottom Right", "Top Left", "Bottom Left"], group = G_DS) // ── Inputs: signals ───────────────────────────────────────────────────────── string G_SG = "Signals" string zoneMode = input.string("ATR", "Touch zone", options = ["ATR", "Points"], group = G_SG) float zoneMult = input.float(0.25, "ATR multiple", step = 0.05, group = G_SG) float zonePts = input.float(2.0, "Points", step = 0.25, group = G_SG) int coolBars = input.int(6, "Cooldown (bars) per signal", minval = 0, group = G_SG) bool todayOnly = input.bool(true, "Signals on today's session only", tooltip = "Levels are for today. Turn off to see how today's levels would have behaved on earlier bars (not a real backtest).", group = G_SG) bool useWin = input.bool(true, "Limit to window", inline = "w", group = G_SG) string sessWin = input.session("0930-1600", "", inline = "w", group = G_SG) bool sCWR = input.bool(true, "Call Wall rejection (short)", group = G_SG) bool sPWH = input.bool(true, "Put Wall hold (long)", group = G_SG) bool sGFL = input.bool(true, "Gamma Flip loss (short)", group = G_SG) bool sGFR = input.bool(true, "Gamma Flip reclaim (long)", group = G_SG) bool sRTF = input.bool(true, "Flip retest FAIL from below (short)", group = G_SG) bool sRTH = input.bool(true, "Flip retest HOLD from above (long)", group = G_SG) bool sCWB = input.bool(true, "Call Wall breakout (long)", group = G_SG) bool sPWB = input.bool(true, "Put Wall breakdown (short)", group = G_SG) // ── Inputs: volume-confirmed level signals (optional) ─────────────────────── string G_VS = "Volume-confirmed level signals (optional)" bool useVS = input.bool(false, "Enable volume reversal / break signals", tooltip = "REVERSAL: volume dries up into the level, then a higher-volume candle turns away from it.\nBREAK: a candle closes through the level on above-average volume.", group = G_VS) string vsSet = input.string("Main (CW / PW / Flip)", "Levels to watch", options = ["Main (CW / PW / Flip)", "All (incl. 2nd + 0DTE)"], group = G_VS) bool vsRev = input.bool(true, "Reversal signals", inline = "vt", group = G_VS) bool vsBrk = input.bool(true, "Break signals", inline = "vt", group = G_VS) int volLen = input.int(20, "Volume average length", minval = 2, group = G_VS) int touchBars = input.int(5, "Approach window (bars)", minval = 2, maxval = 30, tooltip = "Bars in which price must have tested the level (within the touch zone).", group = G_VS) int dryBars = input.int(3, "Dry-up bars", minval = 1, maxval = 10, tooltip = "Average volume of these bars (before the trigger bar) must be below the dry-up threshold.", group = G_VS) float dryMult = input.float(0.8, "Dry-up threshold (× avg vol)", step = 0.05, group = G_VS) float revMult = input.float(1.3, "Reversal bar volume (× avg vol)", step = 0.05, tooltip = "Turn bar must exceed this AND the previous bar's volume.", group = G_VS) float brkMult = input.float(1.5, "Break bar volume (× avg vol)", step = 0.05, group = G_VS) float brkBody = input.float(0.5, "Break bar min body (% of range)", minval = 0, maxval = 1, step = 0.05, tooltip = "0.5 = body must be at least half the candle's range (filters wicky fake breaks).", group = G_VS) // ── Inputs: alerts ────────────────────────────────────────────────────────── string G_AL = "Alerts (JSON webhook via alert())" bool useJson = input.bool(true, "Send JSON alert() for enabled signals", tooltip = "Create one alert with condition 'Any alert() function call'.", group = G_AL) // ════════════════════════════════════════════════════════════════════════════ // PARSE THE PASTED TEXT (once) // ════════════════════════════════════════════════════════════════════════════ f_family(string r) => switch r "ES" => "SPX" "MES" => "SPX" "SPY" => "SPX" "XSP" => "SPX" "NQ" => "NDX" "MNQ" => "NDX" "QQQ" => "NDX" => r f_confName(string s) => switch s "H" => "HIGH" "HIGH" => "HIGH" "G" => "GOOD" "GOOD" => "GOOD" "M" => "MED" "MED" => "MED" "MEDIUM" => "MED" "L" => "LOW" "LOW" => "LOW" => s f_confColor(string s) => switch s "HIGH" => #00E676 "GOOD" => #4FC3F7 "MED" => #FFD600 "LOW" => #FF5252 => #787B86 var float cwRaw = na var float cw2Raw = na var float pwRaw = na var float pw2Raw = na var float gfRaw = na var float cw0Raw = na var float pw0Raw = na var float cwM = na var float cw2M = na var float pwM = na var float pw2M = na var float cw0M = na var float pw0M = na var string confTxt = "" var string lineName = "" var string mode = "None" if barstate.isfirst string txt = str.replace_all(levelsTxt, "\n", "|") txt := str.replace_all(txt, " ", "") txt := str.replace_all(txt, ",", ";") string tick = str.upper(syminfo.ticker) string root = str.upper(syminfo.root) string fam = f_family(root) string want = str.upper(str.replace_all(blockOverride, " ", "")) string exact = "" string exactName = "" string famBody = "" string forced = "" string generic = "" for b in str.split(txt, "|") if str.length(b) == 0 continue int colon = str.pos(b, ":") int eq = str.pos(b, "=") bool hasName = not na(colon) and (na(eq) or colon < eq) if hasName string nm = str.upper(str.substring(b, 0, colon)) string body = str.substring(b, colon + 1) if want != "" and nm == want forced := body if nm == tick or nm == root exact := body exactName := nm else if nm == fam and famBody == "" famBody := body else if generic == "" generic := b string use = "" if forced != "" use := forced lineName := want else if exact != "" use := exact lineName := exactName else if famBody != "" use := famBody lineName := fam else if generic != "" use := generic lineName := "(generic)" for kv in str.split(use, ";") array p = str.split(kv, "=") if array.size(p) == 2 string k = str.upper(array.get(p, 0)) string sv = array.get(p, 1) if k == "CONF" confTxt := f_confName(str.upper(sv)) else float v = str.tonumber(sv) switch k "CW" => cwRaw := v "CW2" => cw2Raw := v "PW" => pwRaw := v "PW2" => pw2Raw := v "GF" => gfRaw := v "CW0" => cw0Raw := v "PW0" => pw0Raw := v "CWM" => cwM := v "CW2M" => cw2M := v "PWM" => pwM := v "PW2M" => pw2M := v "CW0M" => cw0M := v "PW0M" => pw0M := v // resolve conversion mode bool foreign = lineName != "" and lineName != "(generic)" and lineName != tick and lineName != root mode := switch convMode "None" => "None" "Spread (futures)" => "Spread" "Ratio (ETF)" => "Ratio" => foreign ? (syminfo.type == "futures" ? "Spread" : "Ratio") : "None" // ════════════════════════════════════════════════════════════════════════════ // LIVE BASIS / RATIO // gaps_on → value only when the index prints a bar, so overnight futures bars // keep the last regular-session basis instead of using a stale index close. // ════════════════════════════════════════════════════════════════════════════ float srcClose = request.security(srcSym, timeframe.period, close, gaps = barmerge.gaps_on, lookahead = barmerge.lookahead_off) var float basis = na var float ratio = na if not na(srcClose) and srcClose != 0 basis := close - srcClose ratio := close / srcClose f_adj(float lvl) => float b = manualAdj != 0 ? manualAdj : basis float r = manualAdj != 0 ? manualAdj : ratio switch mode "Spread" => lvl + b "Ratio" => lvl * r => lvl float cw = f_adj(cwRaw) float cw2 = f_adj(cw2Raw) float pw = f_adj(pwRaw) float pw2 = f_adj(pw2Raw) float gf = f_adj(gfRaw) float cw0 = f_adj(cw0Raw) float pw0 = f_adj(pw0Raw) // ════════════════════════════════════════════════════════════════════════════ // DRAW LEVELS (rebuilt on the last bar so they follow the live basis) // ════════════════════════════════════════════════════════════════════════════ var array lns = array.new() var array