Skip to Main Content
Back to website
Indicator Disclaimer | Report this indicator

Options Ladder Pro (◇ OLP)

By: Dskyz | DAFE Trading Systems

Most options tools show you a chain. The Ladder shows you where the weight is. It pulls open interest from the nearest expirations, stacks it by strike into a live ladder, finds the magnet (POC) and the 70% value area, and then asks the only question that matters intraday: is price being pulled toward the mass or escaping it? When there is no chain — futures, index, crypto, after hours — it says so and builds the same ladder from volume, labeled.

#TSBuild26 (Most Unique Options Indicator)

How it works — in 6 steps:

  1. Data Source Auto detects the instrument: equities take the live chain, futures / index / crypto take the SLF volume-profile ladder, and a labeled Advanced Synthetic tenor surface stands in when a chain is requested but unavailable. The source tag is always printed.
  2. Up to five nearest expirations are requested in one parallel round-trip — one schedule call plus one chain call per expiry, capped at the platform's six — and merged into one strike surface with OI, OI change and daily volume; a failed expiry is skipped and counted, never faked.
  3. The Resolution Cascade syncs strike spacing to your visible range: zoom in for every strike, zoom out for the significant ones.
  4. The surface yields the POC (max-OI strike), VAH / VAL (70% of OI) and smart rails at the strikes bracketing price; a put/call ratio and five-zone mass split read where the weight sits above, around and below value.
  5. Signals are structural: POC cross, value-area breakout, value-area rejection, and end-of-tape flow shifts — gated by a zero-lag MFI / CCI momentum filter and a sensitivity dial, with a wick-pressure kernel grading peaks and valleys, entropy filtering chop and fractal/pivot confluence.
  6. Every signal is intra-bar capable (orange LIVE on the forming bar), confirms on close, and is registered for alerts: VA/POC Long, VA/POC Short, Valley Long, Peak Short.

Every system with guides:
Input Guide — every menu item explained in plain language.
Visual Guide — every line, marker, board column and dial wedge explained.

What you see on chart:
POC line in gold that breathes and flashes when crossed; VAH / VAL that dim inside value and brighten on escape; dotted strike rails bracketing price; ▲ POC / ▼ POC cross markers; peak and valley markers with orange LIVE previews.

The Board:
Strike ladder — PUTS bar left, strike and tag center, CALLS bar right; the POC row pulses gold, fresh-flow rows (new OI today) glow, a traveling highlight scans the rows.
OI Profile — the horizontal open-interest-by-strike chart inside the board.
Mass Split dial — five solid wedges, radius = share of total OI, hue = put/call balance: ABV above VAH, UVA POC→VAH, POC the magnet, LVA VAL→POC, BLW below VAL — percentages printed beside each label.

Dashboard reads live: source and session state, expirations loaded, POC / VAH / VAL and price position, put/call ratio and flow shift, the signal state with its reason, momentum-filter reads, and the ladder itself.

Limits (know them):
Open interest is yesterday's settled positioning; OI change and daily volume are today's — the Fresh-Flow glow is how you tell them apart.
No chain on futures, index or crypto: the ladder is volume (SLF) and labeled as such.
Levels are magnets and fences, not forecasts — a POC cross is a structure event, never a guaranteed move.
Alerts fire on confirmed closes; orange LIVE markers may vanish.

Find the mass. Trade the escape. Respect the magnet.
— Dskyz | DAFE Trading Systems

describe_indicator('Options Ladder Pro [DAFE]', 'overlay', { shortName: '◇ OLP', mainColorInheritFrom: 'legend_anchor' });
// ==============================================================================================
// ------------------  Options Ladder Pro [DAFE]  -------------------
// ==============================================================================================
// LIBRARIES & DAFE QUANT CORE & HELPERS
// ==============================================================================================
var jStat    = library('jstat');
var bsBounds = library('binary-search-bounds');
var fftjs; try { fftjs = library('fft-js'); } catch (eFft) { fftjs = null; }
var tiny     = library('tinycolor2');
var DAFE = { utils: {} };
DAFE.utils.clamp = function(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); };
DAFE.utils.nz = function(v, fb) { return (v == null || v !== v || !isFinite(v)) ? (fb || 0) : v; };
DAFE.utils.make = function(len, val) { var a = [], i; for (i = 0; i < len; i++) a.push(val); return a; };
function clamp(v, lo, hi) { return DAFE.utils.clamp(v, lo, hi); }
function clamp01(v) { return clamp(v, 0, 1); }
function nz(v, fb) { return DAFE.utils.nz(v, fb); }
function make(len, val) { return DAFE.utils.make(len, val); }
function make_array(len, val) { return make(len, val); }
function hexToRgba(c, a) { var h = String(c || '').replace('#', ''); if (h.length === 3) h = h.charAt(0) + h.charAt(0) + h.charAt(1) + h.charAt(1) + h.charAt(2) + h.charAt(2); if (!/^[0-9a-fA-F]{6}$/.test(h)) return c; return 'rgba(' + parseInt(h.slice(0, 2), 16) + ', ' + parseInt(h.slice(2, 4), 16) + ', ' + parseInt(h.slice(4, 6), 16) + ', ' + Math.max(0, Math.min(1, a)) + ')'; }
function rgba(c, a) { try { if (tiny) return tiny(c).setAlpha(a).toRgbString(); } catch (eR) {} return hexToRgba(c, a); }   // alpha survives a missing colour library
var EPS = 1e-10;
function rangeOpts(min, max, step, dec) {
  var out = [], steps = Math.round((max - min) / step);
  for (var k = 0; k <= steps; k++) {
    var v = min + k * step; v = parseFloat(v.toFixed(dec !== undefined ? dec : 6)); out.push(v);}
  return out;}
function tt(text) { return { tooltip: text, description: text, help: text }; }
// ============================================================================
// INPUTS — all dropdowns, consolidated
// ============================================================================
var i_sourceMode   = input.select('Data Source', 'Auto', ['Auto', 'Live Options', 'Advanced Synthetic', 'SLF'], tt('Auto detects the instrument: futures/index/crypto get SLF (volume profile), equities get Live Options chain with Advanced Synthetic fallback. Override to force a source.'));
var i_resMode      = input.select('Resolution', 'Auto (Range-Synced)', ['Auto (Range-Synced)', 'Fine (0.5x)', 'Normal (1x)', 'Coarse (2x)'], tt('The Resolution Cascade: Auto syncs strike spacing to your visible chart range — zoom in for tight strikes, zoom out for significant ones. Fixed modes lock the spacing multiplier.'));
var i_numExp       = input.select('Expirations', '1', ['1', '2', '3', '4', '5'], tt('Number of nearest expirations to aggregate into the OI surface. More = fuller magnet structure but slower.'));
var i_sensMode     = input.select('Signal Sensitivity', 'Balanced', ['Loose', 'Balanced', 'Strict'], tt('Signal spacing and flow-shift thresholds. Loose = more signals. Strict = fewer, higher-conviction.'));
var i_intrabar     = input.select('Intra-bar Signals', 'On', ['Off', 'On'], tt('On: signals on the forming bar show as orange LIVE markers and confirm or vanish at close. Off: signals only on closed bars. Alerts always fire on confirmed close.'));
var i_embedMode    = input.select('Embed Charts', 'Both', ['Off', 'Profile', 'Split', 'Both'], tt('Embedded Chart.js inside the board: Profile = horizontal OI-by-strike bar chart. Split = the Mass Split dial (OI mass by value-area zone with put/call hue). Both = profile and dial side by side. Terrain = 3D strike x tenor surface in a bottom overlay — spectacular.'));
var i_cameraStyle  = input.select('Camera Style', 'Cinematic', ['Static Card', 'Slow Drift', 'Cinematic', 'Rapid Spin'], tt('Camera orbit for the terrain. Static Card = fixed view. Cinematic = slow orbit with tilt. Rapid Spin = fast perspective orbit.'));
var i_cometSpeed   = input.select('Highlight Scan', 'Off', ['Off', 'Fast', 'Slow'], tt('The traveling highlight box that scans down the ladder rows. Fast = ~0.5s per row. Slow = ~2s per row. Off = static.'));
var i_freshGlow    = input.select('Fresh-Flow Glow', 'Off', ['Off', 'On'], tt('Strikes with new positioning today (OI change ≠ 0 or daily volume) glow brighter than static OI rows. Live-data dimension.'));
var i_textSize      = input.select('Dashboard Text Size', 'Large', ['Small', 'Medium', 'Large'], tt('Scales all dashboard typography (Large matches original default).'));
var i_theme        = input.select('Theme', 'Neon Night', ['Neon Night', 'Cyber Punk', 'Emerald Abyss', 'Gold Sovereign'], tt('Four color skins driving every overlay, cloud, marker, raster and dashboard. Cosmetic only.'));
var i_guideMode    = input.select('Guide Panel', 'Off', ['Off', 'Input Guide', 'Visual Guide'], tt('Reference panels. Cosmetic only.'));
var i_showInputGuide  = i_guideMode === 'Input Guide';
var i_showVisualGuide = i_guideMode === 'Visual Guide';
var i_visRange     = input.anchor('visible range');
var CAM_MAP = {
  'Static Card': { auto: false, rate: 0, spin: 25, tilt: 24, proj: 'Isometric' },
  'Slow Drift':  { auto: true, rate: 3, spin: 20, tilt: 26, proj: 'Isometric' },
  'Cinematic':   { auto: true, rate: 6, spin: 30, tilt: 30, proj: 'Perspective' },
  'Rapid Spin':  { auto: true, rate: 14, spin: 0, tilt: 34, proj: 'Perspective' } };
var CAM = CAM_MAP[i_cameraStyle] || CAM_MAP['Cinematic'];
var SURF = { numExp: parseInt(i_numExp, 10) || 3, rows: 21, lookback: 120 };
var SYN = { window: 6.0, aggression: 1.45, glow: 0.22, memory: 0.92 };
var LIB_OK = true;
try { if (typeof jStat === 'undefined' || !jStat) LIB_OK = false; } catch (e) { LIB_OK = false; }
var n = close.length;
var i;
var lastIdx = n - 1;
var currentPrice = close[lastIdx];
var CLOCK_EARLY = (function(){
  var w = 0, has = false;
  try {
    if (typeof Date !== 'undefined' && typeof Date.now === 'function') {
      w = Date.now();
      if (w > 0) has = true; }
  } catch (e) {}
  if (!has) w = n * 1000;
  var c = { wallMs: w, hasClock: has, barMod8: (n % 8) / 8, barMod12: (n % 12) / 12 };
  c.phase12 = c.barMod12 * 2 * Math.PI;
  c.slow = (c.wallMs / 1000) % 100000;
  c.render = c.hasClock ? c.wallMs : n * 250;
  c.phase8 = c.barMod8 * 2 * Math.PI;
  c.beat = c.hasClock ? (c.wallMs / 1000) : n;
  return c;})();
var CLOCK = CLOCK_EARLY;
function _barTimeMs(idx) {
  var t = 0;
  try { t = nz(time[idx], 0); } catch (eT) { t = 0; }
  if (t > 0 && t < 1e12) t *= 1000; 
  return t;}
function _dowUTC(ms) {
  var days = Math.floor(ms / 86400000);
  return ((days % 7) + 7 + 4) % 7; }
function _civilYearFromMs(ms) {
  var z = Math.floor(ms / 86400000) + 719468;
  var era = Math.floor((z >= 0 ? z : z - 146096) / 146097);
  var doe = z - era * 146097;
  var yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365);
  var y = yoe + era * 400;
  var doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100));
  var mp = Math.floor((5 * doy + 2) / 153);
  var m = mp + (mp < 10 ? 3 : -9);
  return y + (m <= 2 ? 1 : 0);}
function _nthSundayUTC(year, month0, nth) {
  var day1ms = Date.UTC(year, month0, 1);
  var dow = _dowUTC(day1ms);
  var firstSun = 1 + ((7 - dow) % 7);
  return firstSun + (nth - 1) * 7;}
function _etHourOf(ms) {
  var y = _civilYearFromMs(ms);
  var dstStart = Date.UTC(y, 2, _nthSundayUTC(y, 2, 2), 7);
  var dstEnd   = Date.UTC(y, 10, _nthSundayUTC(y, 10, 1), 6);
  var offset = (ms >= dstStart && ms < dstEnd) ? 4 : 5;
  var frac = (ms % 86400000) / 3600000;
  var et = frac - offset;
  if (et < 0) et += 24;
  return et;}
function _etWeekdayOf(ms) {
  var dow = _dowUTC(ms);
  var offset = 5;
  try {
    var y = _civilYearFromMs(ms);
    var dstStart = Date.UTC(y, 2, _nthSundayUTC(y, 2, 2), 7);
    var dstEnd = Date.UTC(y, 10, _nthSundayUTC(y, 10, 1), 6);
    offset = (ms >= dstStart && ms < dstEnd) ? 4 : 5;
  } catch (eDow) {}
  var frac = (ms % 86400000) / 3600000;
  var crossedBack = frac < offset; 
  return crossedBack ? ((dow + 6) % 7) : dow;}
function _isExtendedSessionInstrument() {
  try {
    var ex = String((typeof constants !== 'undefined' && constants && constants.exchange) || '').toUpperCase();
    if (ex === 'CME' || ex === 'CBOT' || ex === 'NYMEX' || ex === 'COMEX' || ex === 'NYBOT') return true;
    var ticker = String((typeof constants !== 'undefined' && constants && constants.ticker) || '').toUpperCase();
    if (/USD$|USDT$|BTC|ETH|XRP|SOL/.test(ticker)) return true;
    if (/.(FUT|F)$/i.test(ticker) || ticker.indexOf('_FV') >= 0) return true;
  } catch (eX) {}
  return false;}
function sessionState() {
  var ms = _barTimeMs(typeof lastIdx === 'number' ? lastIdx : (n - 1));
  if (!(ms > 0)) return { live: false, ah: false, label: 'no-time' };
  var et = _etHourOf(ms), dow = _etWeekdayOf(ms);
  var barMs = 60000;
  if (n >= 2) {
    var t1 = _barTimeMs(n - 1), t0 = _barTimeMs(n - 2);
    if (t1 > t0 && t1 - t0 < 8.64e7) barMs = t1 - t0;}
  var wall = CLOCK.hasClock ? CLOCK.wallMs : ms;
  var barAgeMin = (wall - ms) / 60000;
  var recent = barAgeMin <= Math.max(5, (barMs / 60000) * 2.5);
  var inRegHours;
  if (_isExtendedSessionInstrument()) {
    var halted = (dow === 6) || (dow === 0 && et < 18) || (dow === 5 && et >= 17) || (et >= 17 && et < 18);
    inRegHours = !halted;
  } else {
    inRegHours = (dow >= 1 && dow <= 5) && et >= 9.5 && et < 16;}
  var live = inRegHours && recent;
  var label = !inRegHours ? 'CLOSED' : !recent ? 'LAST CLOSE' : 'LIVE';
  return { live: live, ah: !inRegHours, label: label, barAgeMin: barAgeMin };}
// ==============================================================================================
// INTERNAL MAPPINGS & DASHBOARD DIMENSIONS
// ==============================================================================================
var numExp = parseInt(i_numExp, 10) || 3;
var RES_MAP = { 'Auto (Range-Synced)': null, 'Fine (0.5x)': 0.5, 'Normal (1.0x)': 1.0, 'Coarse (2.0x)': 2.0, 'Macro (4.0x)': 4.0 };
var SENS_MAP = {
  'Loose':    { minDist: 5,  pcrHi: 1.25, pcrLo: 0.80 },
  'Balanced': { minDist: 10, pcrHi: 1.15, pcrLo: 0.85 },
  'Strict':   { minDist: 15, pcrHi: 1.05, pcrLo: 0.95 }};
var SN = SENS_MAP[i_sensMode] || SENS_MAP['Balanced'];
var SENS = SENS_MAP[i_sensMode] || SENS_MAP['Balanced'];
var baseFontSize = i_textSize === 'Small' ? 6 : (i_textSize === 'Medium' ? 7 : 8);
var headFontSize = baseFontSize + 1;
var subFontSize  = Math.max(7, baseFontSize - 1);
var BOARD_WIDTH_CONFIG = {
  'Large':  { 'Off': 270, 'Split': 300, 'Profile': 330, 'Both': 390, 'Terrain': 340 },
  'Medium': { 'Off': 240, 'Split': 270, 'Profile': 300, 'Both': 350, 'Terrain': 310 },
  'Small':  { 'Off': 215, 'Split': 240, 'Profile': 270, 'Both': 315, 'Terrain': 280 } };
var boardWidthPx = (BOARD_WIDTH_CONFIG[i_textSize] && BOARD_WIDTH_CONFIG[i_textSize][i_embedMode]) || 340;
var boardWidth = boardWidthPx + 'px';
var SURF = { numExp: parseInt(i_numExp, 10) || 3, rows: 21, lookback: 120 };
var SYN = { window: 6.0, aggression: 1.45, glow: 0.22, memory: 0.92 };
var RADAR = { enabled: true, decay: 0.88, gain: 1.35 };
// ============================================================================
// THEME
// ============================================================================
var THEME_MAP = {
  'Neon Night':     { bull: '#00C853', bear: '#D32F2F', a1: '#4DA3FF', a2: '#D34BD3', a3: '#80DEEA', warn: '#FFA500', gold: '#FFD700', txt: '#E0E0FF', dim: '#8888AA', panel: 'rgba(16,18,42,0.92)', bg: 'rgba(10,11,31,0.96)', ent: '#05589a', acc: '#4DA3FF', pur: '#D34BD3' },
  'Cyber Punk':     { bull: '#00FF00', bear: '#FF007F', a1: '#00FFFF', a2: '#A855F7', a3: '#39FF14', warn: '#FFFF00', gold: '#FFFF00', txt: '#FFE5FF', dim: '#9A7FB0', panel: 'rgba(26,7,46,0.92)', bg: 'rgba(15,5,29,0.96)', ent: '#7B2FBE', acc: '#00FFFF', pur: '#A855F7' },
  'Emerald Abyss':  { bull: '#00FF99', bear: '#FF4500', a1: '#3CB371', a2: '#ADFF2F', a3: '#98FB98', warn: '#FFD700', gold: '#FFD700', txt: '#E0FFE0', dim: '#5D8A6E', panel: 'rgba(5,26,14,0.92)', bg: 'rgba(2,10,5,0.96)', ent: '#2E8B57', acc: '#3CB371', pur: '#98FB98' },
  'Gold Sovereign': { bull: '#FFD700', bear: '#E65100', a1: '#FFB300', a2: '#FFF8E1', a3: '#FFE57F', warn: '#FF6D00', gold: '#FFD700', txt: '#FFF8E1', dim: '#A08B4A', panel: 'rgba(28,21,0,0.92)', bg: 'rgba(10,7,0,0.96)', ent: '#B8860B', acc: '#FFB300', pur: '#FFE57F' }};
var T = THEME_MAP[i_theme] || THEME_MAP['Neon Night'];
if (!T) T = { bull: '#00C853', bear: '#D32F2F', a1: '#4DA3FF', a2: '#D34BD3', a3: '#80DEEA', warn: '#FFA500', gold: '#FFD700', txt: '#E0E0FF', dim: '#8888AA', panel: 'rgba(16,18,42,0.92)', bg: 'rgba(10,11,31,0.96)', ent: '#05589a', acc: '#4DA3FF', pur: '#D34BD3' };
var THEME = T;
var TT = {
	bull: T.bull, bear: T.bear, neu: T.a2 || T.a1, prim: T.a1, sec: T.a3 || T.a1,
	accent: T.a1, ghost: T.dim, hi: T.txt, lo: T.dim,
	bg: T.bg, panel: T.panel, gold: T.gold, field: T.a1,
	void_: '#05060C', fresh: T.warn, sec2: T.a3};
var PV = { bull: T.bull, bear: T.bear, a1: T.a1, a2: T.a2, a3: T.a3, warn: T.warn, txt: T.txt, dim: T.dim, panel: T.panel, bg: T.bg };
// ============================================================================
// DERIVED CONFIG
// ============================================================================
var n = close.length;
var i;
var lastIdx = n - 1;
var currentPrice = close[lastIdx];
var SESSION = sessionState();
var adaptivePeriod = 30;
var ema21 = ema(close, 21);
var ema55 = ema(close, 55);
var ema200 = ema(close, 200);
var atr14 = atr(14);
var roc10 = roc(close, 10);
var atrFast = sma(atr14, 10);
var atrSlow = sma(atr14, 50);
var volSma20 = sma(volume, 20);
var relVolArr = make(n, 1);
var logRet = make(n, 0);
var downMoves = make(n, 0), upMoves = make(n, 0);
for (var _ci = 0; _ci < n; _ci++) { relVolArr[_ci] = volSma20[_ci] > 0 ? nz(volume[_ci], 0) / volSma20[_ci] : 1;
  if (_ci > 0) logRet[_ci] = Math.log(Math.max(close[_ci], EPS) / Math.max(close[_ci - 1], EPS));
  if (_ci > 0) { var chg = close[_ci] - close[_ci - 1]; if (chg < 0) downMoves[_ci] = Math.abs(chg); else upMoves[_ci] = chg; } }
var downAvg = sma(downMoves, 14), upAvg = sma(upMoves, 14);
var synPCRLast = clamp(safeDiv(nz(downAvg[lastIdx], 1), nz(upAvg[lastIdx], 1), 1.0), 0.3, 3.0);
var trendEMA = ema(close, adaptivePeriod);
var hlcc4 = make(n, 0);
for (i = 0; i < n; i++) hlcc4[i] = (high[i] + low[i] + close[i] + close[i]) / 4;
var hlcc4_wma = wma(hlcc4, 3);
var velocity = make(n, 0);
var acceleration = make(n, 0);
var velHalf = Math.max(1, Math.round(adaptivePeriod / 2));
for (i = 0; i < n; i++) { velocity[i] = (i >= velHalf) ? hlcc4_wma[i] - hlcc4_wma[i - velHalf] : 0; acceleration[i] = (i > 0) ? velocity[i] - velocity[i - 1] : 0;}
var atr14 = atr(14);
var deltaProxyArr = make(n, 0);
for (i = 1; i < n; i++) deltaProxyArr[i] = clamp((close[i] - close[i - 1]) / Math.max(atr14[i] || 1, EPS), -3, 3);
var SENS_MAP = {
  'Loose':    { minDist: 5,  pcrHi: 1.25, pcrLo: 0.80 },
  'Balanced': { minDist: 10, pcrHi: 1.15, pcrLo: 0.85 },
  'Strict':   { minDist: 15, pcrHi: 1.05, pcrLo: 0.95 }};
var SN = SENS_MAP[i_sensMode];
var minSignalDistance = SN.minDist;
var pcrHiThresh = SN.pcrHi, pcrLoThresh = SN.pcrLo;
var numExp = parseInt(i_numExp, 10);
// ============================================================================
// MOMENTUM FILTER 
// ============================================================================
var mfiZl = make(n, 50);
var cciArr = make(n, 0);
var vwapArr = make(n, 0);
var tpCum = 0, volCum = 0;
for (i = 0; i < n; i++) {
  var tp = (high[i] + low[i] + close[i]) / 3; tpCum += tp * (volume[i] || 0); volCum += (volume[i] || 0); vwapArr[i] = volCum > EPS ? tpCum / volCum : close[i];}
var ema21 = ema(close, 21);
var ema55 = ema(close, 55);
var ema200 = ema(close, 200);
var volAvg = sma(volume, 20);
// ============================================================================
// INSTRUMENT PROFILE
// ============================================================================
function detectInstrumentProfile() {
  var ticker = String((typeof constants !== 'undefined' && constants && constants.ticker) || '').toUpperCase();
  var isFuturesByRoot = /^(ES|NQ|MNQ|YM|MYM|RTY|MRTY|CL|MCL|GC|MGC|SI|MSI|NG|MNG|ZB|MZB|ZN|MZN|ZF|MZF|ZT|MZT|6E|M6E|6J|M6J|6B|M6B|6A|M6A|HG|PL|PA|ZW|MZW|ZC|MZC|ZS|MZS|CT|KC|SB|CC|LE|HE|GF)/.test(ticker);
  var isFuturesBySuffix = /.(FUT|F)$/i.test(ticker) || ticker.indexOf('_FV') >= 0;
  var isFuturesByExchange = constants.exchange === 'CME' || constants.exchange === 'CBOT' || constants.exchange === 'NYMEX' || constants.exchange === 'COMEX';
  var likelyFuturesByPrice = close[lastIdx] > 1000 && !/^[A-Z]{4}$/.test(ticker);
  var isIndex = /^(SPX|NDX|DJX|RUT|VIX|XSP|NSE|DAX|FTSE|CAC|NIKKEI)/i.test(ticker) || ticker.indexOf('I:SPX') >= 0 || ticker.indexOf('TVC:') >= 0;
  var isCrypto = /USD$|USDT$|BTC|ETH|XRP|SOL|DOGE/i.test(ticker) && !/.P$/i.test(ticker);
  var isForex = /^(EUR|GBP|JPY|AUD|CAD|CHF|NZD)[/.]?(USD|EUR|GBP|JPY|AUD|CAD|CHF|NZD)?$/i.test(ticker);
  var avgVol = 0, avCnt = 0;
  for (var av = Math.max(0, n - 20); av < n; av++) { avgVol += (volume[av] || 0); avCnt++; }
  avgVol = avCnt > 0 ? avgVol / avCnt : 0;
  var atrPct = atr14[lastIdx] > 0 ? (atr14[lastIdx] / close[lastIdx]) * 100 : 0;
  var hasOptionsChain = !isFuturesByRoot && !isFuturesBySuffix && !isFuturesByExchange && !isIndex && !isCrypto && !isForex;
  var finalIsFutures = isFuturesByRoot || isFuturesBySuffix || isFuturesByExchange;
  var reason = finalIsFutures ? (isFuturesByRoot ? 'FUTURES_ROOT_MATCH' : isFuturesBySuffix ? 'FUTURES_SUFFIX_MATCH' : isFuturesByExchange ? 'FUTURES_EXCHANGE_MATCH' : 'FUTURES_PRICE_HEURISTIC') : isIndex ? 'INDEX_NO_CHAIN' : isCrypto ? 'CRYPTO_SPOT' : isForex ? 'FOREX_SPOT' : hasOptionsChain ? 'EQUITY_OPTIONS_OK' : 'UNKNOWN_USE_SLF';
  return { ticker: ticker, isFutures: finalIsFutures, isIndex: isIndex, isCrypto: isCrypto, isForex: isForex, hasOptionsChain: hasOptionsChain, avgVolume: avgVol, atrPct: atrPct, reason: reason, confidence: (finalIsFutures || hasOptionsChain) ? 'HIGH' : 'MEDIUM' };}
// ============================================================================
// HELPERS
// ============================================================================
function sortNum(a, b) { return a - b; }
function roundToStep(v, step) { return Math.round(v / step) * step; }
function gaussian(x, mu, sigma) { var s = Math.max(1e-8, sigma), z = (x - mu) / s; return Math.exp(-0.5 * z * z); }
function percentile(arr, p) {
  if (!arr.length) return 0;
  var sorted = arr.slice().sort(function (a, b) { return a - b; });
  var idx = clamp(Math.floor(p * sorted.length), 0, sorted.length - 1);
  return sorted[idx];}
function safeDiv(a, b, c) { return (b && b !== 0 && !isNaN(b)) ? (a / b) : c; }
function repeatStr(s, n) { var out = ''; for (var r = 0; r < n; r++) out += s; return out; }
function niceNum(x) {
  if (!(x > 0) || !isFinite(x)) return 1;
  var exp = Math.floor(Math.log(x) / Math.LN10);
  var f = x / Math.pow(10, exp);
  var nf = f <= 1.25 ? 1 : f <= 2.25 ? 2 : f <= 3.75 ? 2.5 : f <= 7.5 ? 5 : 10;
  return nf * Math.pow(10, exp);}
function inferPriceStep(spot, atrV, rangeW) {
  var raw = (rangeW > 0)
    ? rangeW / 20
    : Math.max(nz(atrV, 0) * 0.4, Math.abs(nz(spot, 0)) * 0.0002, 0.01);
  var step = niceNum(Math.max(raw, 0.01));
  if (Math.abs(spot) >= 1000 && step < 1) step = 1;
  if (Math.abs(spot) >= 1000 && step === 2.5) step = 5;
  return step;}
function medianStrikeStep(strikes) {
  if (!strikes || strikes.length < 2) return null;
  var diffs = [], iD;
  for (iD = 1; iD < strikes.length; iD++) {
    var d = strikes[iD] - strikes[iD - 1];
    if (d > EPS) diffs.push(d);}
  if (!diffs.length) return null;
  diffs.sort(sortNum);
  return diffs[Math.floor(diffs.length * 0.5)];}
function localPriceWindow(spot, atrV) {
  var hi = -Infinity, lo = Infinity, iW;
  var bars = 120;
  try { if (typeof CASCADE !== 'undefined' && CASCADE && CASCADE.bars) bars = CASCADE.bars; } catch (eC) {}
  bars = clamp(Math.max(bars, SURF.lookback || 120), 20, 500);
  var start = Math.max(0, n - bars);
  for (iW = start; iW < n; iW++) {
    if (high[iW] > hi) hi = high[iW];
    if (low[iW] < lo) lo = low[iW];}
  if (!isFinite(hi) || !isFinite(lo) || hi <= lo) {
    var w0 = Math.max(atrV * 4, Math.abs(spot) * 0.008, 1);
    return { lo: spot - w0, hi: spot + w0 };}
  var pad = Math.max(atrV * 0.75, (hi - lo) * 0.08);
  return { lo: lo - pad, hi: hi + pad };}
function fmtPrice(v) {
  if (v == null || !isFinite(v)) return '—';
  var step = (typeof PRICE_STEP === 'number' && PRICE_STEP > 0) ? PRICE_STEP : 0;
  var x = step > 0 ? roundToStep(v, step) : v;
  var a = Math.abs(x);
  if (step >= 1 || a >= 1000) return Math.round(x).toString();
  if (Math.abs(x - Math.round(x)) < 1e-6) return Math.round(x).toString();
  if (Math.abs(x * 2 - Math.round(x * 2)) < 1e-6) return (Math.round(x * 2) / 2).toFixed(1);
  if (a >= 1) {
    var s2 = x.toFixed(2);
    if (s2.slice(-3) === '.00') return s2.slice(0, -3);
    if (s2.slice(-1) === '0') return s2.slice(0, -1);
    return s2;}
  if (a >= 0.1) return x.toFixed(4);
  if (a >= 0.01) return x.toFixed(5);
  return x.toFixed(6);}
function fmtOI(v) {
  if (v >= 1e6) return (v / 1e6).toFixed(2) + 'M';
  if (v >= 1e3) return (v / 1e3).toFixed(1) + 'K';
  return v.toFixed(0);}
function fmtSignedPct(v) { return (v >= 0 ? '+' : '') + (v * 100).toFixed(1) + '%'; }
function chooseWindow(strikes, price, count) {
  if (strikes.length <= count) return strikes.slice();
  var result = [price];
  var lo = [], hi = [];
  for (var i = 0; i < strikes.length; i++) {
    if (strikes[i] < price) lo.push(strikes[i]); else if (strikes[i] > price) hi.push(strikes[i]);}
  lo.sort(function (a, b) { return b - a; });
  var loN = Math.floor(count / 2), hiN = count - loN - 1;
  for (var j = 0; j < loN && j < lo.length; j++) result.push(lo[j]);
  for (var k = 0; k < hiN && k < hi.length; k++) result.push(hi[k]);
  return result;}
var PRICE_STEP = inferPriceStep(currentPrice, nz(atr14[lastIdx], 1), 0);
// ============================================================================
// TERRAIN SUPPORT
// ============================================================================
function lerp(a, b, t) { return a + (b - a) * clamp01(t); }
function hashRand(x) { var v = Math.sin(x * 12.9898) * 43758.5453; return v - Math.floor(v); }
function hexToRgba(hex, alpha) {
  var r = /^#?([a-fd]{2})([a-fd]{2})([a-fd]{2})$/i.exec(hex);
  if (!r) return 'rgba(128,128,128,' + alpha + ')';
  return 'rgba(' + parseInt(r[1], 16) + ',' + parseInt(r[2], 16) + ',' + parseInt(r[3], 16) + ',' + alpha + ')'; }
function hx(c) { try { return tiny ? tiny(c).toHexString() : c; } catch (e) { return c; } }
function mixTowards(hexA, hexB, pct) {
  try { if (tiny) return tiny.mix(hexA, hexB, pct).toHexString(); } catch (eM) {}
  return hexA; }
function lighten(hexC, amt) {
  try { if (tiny) return tiny(hexC).lighten(amt).toHexString(); } catch (eL) {}
  return hexC; }
function lineSeries(v) {
  var a = make(n, null);
  if (!isFinite(v)) return a;
  for (var iLs = 0; iLs < n; iLs++) a[iLs] = v;
  return a; }
function specGlyph(mags) {
  var ref = EPS, iSp, s = '', take, h, blocks = ['·', '▂', '▃', '▅', '█'];
  for (iSp = 0; iSp < mags.length; iSp++) if (mags[iSp] > ref) ref = mags[iSp]; take = Math.min(10, mags.length);
  for (iSp = 0; iSp < take; iSp++) { h = clamp(Math.round(clamp01(mags[iSp] / ref) * 4), 0, 4); s += blocks[h]; }
  return s || '··········'; }
function fbNextPow2(v) { var p = 1; v = Math.max(1, v | 0); while (p < v) p <<= 1; return p; }
function fbBitRev(idx, bits) { var rev = 0, i; for (i = 0; i < bits; i++) { rev = (rev << 1) | (idx & 1); idx >>= 1; } return rev; }
function fbFftComplex(reIn, imIn) {
    var nfft = reIn.length;
    if (nfft < 2 || (nfft & (nfft - 1))) return { re: reIn.slice(0), im: imIn.slice(0) };
    var bits = 0, tmp = nfft; while (tmp > 1) { tmp >>= 1; bits++; }
    var re = reIn.slice(0), im = imIn.slice(0);
    var i, j, size, half, wr, wi, evenR, evenI, oddR, oddI, tr, ti, k, ang;
    for (i = 0; i < nfft; i++) { j = fbBitRev(i, bits); if (j > i) { tmp = re[i]; re[i] = re[j]; re[j] = tmp; tmp = im[i]; im[i] = im[j]; im[j] = tmp; } }
    for (size = 2; size <= nfft; size <<= 1) { half = size >> 1;
        for (i = 0; i < nfft; i += size) {
            for (k = 0; k < half; k++) {
                ang = (-2 * Math.PI / size) * k; wr = Math.cos(ang); wi = Math.sin(ang);
                evenR = re[i + k]; evenI = im[i + k]; oddR = re[i + k + half]; oddI = im[i + k + half];
                tr = wr * oddR - wi * oddI; ti = wr * oddI + wi * oddR;
                re[i + k] = evenR + tr; im[i + k] = evenI + ti; re[i + k + half] = evenR - tr; im[i + k + half] = evenI - ti; } } }
    return { re: re, im: im };}
function fbFft(signal) {
    var nfft = fbNextPow2(signal.length);
    var re = make_array(nfft, 0), im = make_array(nfft, 0), i, out, ph = [];
    for (i = 0; i < signal.length && i < nfft; i++) re[i] = nz(signal[i], 0); out = fbFftComplex(re, im);
    for (i = 0; i < nfft; i++) ph.push([out.re[i], out.im[i]]);
    return ph;}
function fbIfft(phasors) {
    var nfft = phasors.length, re = [], im = [], i, out, sig = [];
    for (i = 0; i < nfft; i++) { re.push(nz(phasors[i][0], 0)); im.push(-nz(phasors[i][1], 0)); }
    out = fbFftComplex(re, im);
    for (i = 0; i < nfft; i++) sig.push(out.re[i] / nfft);
    return sig;}
function fbMag(ph) { var m = [], i, a, b; for (i = 0; i < ph.length; i++) { a = nz(ph[i][0], 0); b = nz(ph[i][1], 0); m.push(Math.sqrt(a * a + b * b)); } return m; }
function fbAng(ph) { var a = [], i; for (i = 0; i < ph.length; i++) a.push(Math.atan2(nz(ph[i][1], 0), nz(ph[i][0], 0))); return a; }
var QC = {};
QC.hann = function (len, i) { if (len <= 1) return 1; return 0.5 * (1 - Math.cos((2 * Math.PI * i) / (len - 1))); };
QC.fft = function (padded) { return LIB_OK ? fftjs.fft(padded) : fbFft(padded); };
QC.ifft = function (ph) { return LIB_OK ? fftjs.ifft(ph) : fbIfft(ph); };
QC.fftMag = function (ph) { return LIB_OK ? fftjs.util.fftMag(ph) : fbMag(ph); };
QC.fftAng = function (ph) { return LIB_OK ? fftjs.util.fftAng(ph) : fbAng(ph); };
QC.spectrum = function (values) {
    var nfft = fbNextPow2(values.length), padded = values.slice(0), i;
    for (i = values.length; i < nfft; i++) padded.push(0);
    var ph = QC.fft(padded);
    return { ph: ph, mag: QC.fftMag(ph), ang: QC.fftAng(ph), nfft: nfft }; };
QC.lowpass = function (values, keepFrac) {
    var sp = QC.spectrum(values), ph = sp.ph, nfft = sp.nfft, keep, i, rec, out = [];
    keep = Math.max(2, Math.floor(nfft * clamp(keepFrac || 0.12, 0.04, 0.45)));
    for (i = keep; i < nfft - keep; i++) { ph[i][0] = 0; ph[i][1] = 0; }
    rec = QC.ifft(ph);
    for (i = 0; i < values.length && i < rec.length; i++) out.push(rec[i]);
    return out; };
function fbMean(a) { var s = 0, c = 0, i; for (i = 0; i < a.length; i++) if (isFinite(a[i])) { s += a[i]; c++; } return c ? s / c : 0; }
function fbStdev(a, sample) { var mu = fbMean(a), s = 0, c = 0, i, d; for (i = 0; i < a.length; i++) if (isFinite(a[i])) { d = a[i] - mu; s += d * d; c++; } if (c < 2) return 0; return Math.sqrt(s / (sample ? c - 1 : c)); }
QC.mean = function (a) { return LIB_OK ? jStat.mean(a) : fbMean(a); };
QC.stdev = function (a, sample) { try { return LIB_OK ? jStat.stdev(a, !!sample) : fbStdev(a, sample); } catch (e) { return fbStdev(a, sample); } };
QC.sum = function (a) { var s = 0, i; for (i = 0; i < a.length; i++) s += nz(a[i], 0); return s; };
QC.skewness = function (a) { try { return LIB_OK ? jStat.skewness(a) : 0; } catch (e) { return 0; } };
QC.kurtosis = function (a) { try { return LIB_OK ? jStat.kurtosis(a) : 0; } catch (e) { return 0; } };
QC.corrcoeff = function (a, b) {
    var len = Math.min(a.length, b.length), i, xa = [], xb = [];
    for (i = 0; i < len; i++) if (isFinite(a[i]) && isFinite(b[i])) { xa.push(a[i]); xb.push(b[i]); }
    if (xa.length < 3) return 0;
    try { return LIB_OK ? jStat.corrcoeff(xa, xb) : 0; } catch (e) { return 0; } };
QC.percentile = function (arr, p) {
    var xs = [], i; for (i = 0; i < arr.length; i++) if (isFinite(arr[i])) xs.push(arr[i]);
    if (!xs.length) return 0;
    xs.sort(sortNum);
    try { if (LIB_OK) return jStat.percentile(xs, clamp(p, 0, 1)); } catch (e) {}
    var t = clamp(p, 0, 1) * (xs.length - 1), lo = Math.floor(t), hi = Math.ceil(t);
    return lo === hi ? xs[lo] : xs[lo] + (xs[hi] - xs[lo]) * (t - lo); };
QC.percentileOfScore = function (arr, score) {
    var xs = [], i; for (i = 0; i < arr.length; i++) if (isFinite(arr[i])) xs.push(arr[i]);
    if (!xs.length) return 0;
    try { if (LIB_OK) return jStat.percentileOfScore(xs, score); } catch (e) {}
    var c = 0; for (i = 0; i < xs.length; i++) if (xs[i] <= score) c++;
    return c / xs.length; };
QC.normalCdf = function (x, mean, sd) {
    try { if (LIB_OK) return jStat.normal.cdf(x, mean || 0, Math.max(sd || 1, EPS)); } catch (e) {}
    return 0.5; };
QC.shannonH = function (weights) {
    var tot = QC.sum(weights), i, p, h = 0, c = 0;
    if (tot <= EPS) return 1;
    for (i = 0; i < weights.length; i++) { p = nz(weights[i], 0) / tot; if (p > EPS) { h -= p * Math.log(p); c++; } }
    return c > 1 ? h / Math.log(c) : 1; };
QC.nearestIdx = function (sorted, y) {
    if (!sorted.length) return -1;
    var i;
    try { i = LIB_OK ? bsBounds.ge(sorted, y) : -1; } catch (e) { i = -1; }
    if (i < 0) { i = 0; while (i < sorted.length && sorted[i] < y) i++; }
    if (i >= sorted.length) return sorted.length - 1;
    if (i <= 0) return 0;
    return Math.abs(sorted[i] - y) < Math.abs(sorted[i - 1] - y) ? i : i - 1; };
QC.nearestKey = function (sortedKeys, y, maxDist) {
    var i = QC.nearestIdx(sortedKeys, y);
    if (i < 0) return null;
    if (maxDist != null && Math.abs(sortedKeys[i] - y) > maxDist) return null;
    return sortedKeys[i]; };
function makeCamera() {
    var spinDeg = CAM.spin + (CAM.auto ? (CLOCK.slow * CAM.rate) : 0);
    spinDeg = ((spinDeg % 360) + 360) % 360;
    var tiltEff = CAM.tilt + (CAM.auto ? 1.1 * Math.sin(CLOCK.slow * 0.30) : 0);
    var spinRad = spinDeg * Math.PI / 180, tiltRad = tiltEff * Math.PI / 180;
    return {
        spinDeg: spinDeg,
        cosS: Math.cos(spinRad), sinS: Math.sin(spinRad),
        cosT: Math.cos(tiltRad), sinT: Math.sin(tiltRad),
        cosS2: Math.cos(spinRad * 0.38), sinS2: Math.sin(spinRad * 0.38),
        camDist: 92, mode: CAM.proj }; }
function project3D(cam, x, y, z, parallax) {
    var cS = parallax ? cam.cosS2 : cam.cosS, sS = parallax ? cam.sinS2 : cam.sinS;
    var rx = x * cS + z * sS, rz = -x * sS + z * cS;
    if (cam.mode === 'Perspective') {
        var w = cam.camDist / Math.max(cam.camDist + rz, 8);
        return { x: rx * w, y: ((rx + rz) * cam.sinT + y) * w, z: rz }; }
    return { x: (rx - rz) * cam.cosT, y: (rx + rz) * cam.sinT + y, z: rz }; }
function ringXZ(cam, radius, steps, y0) {
    var pts = [], s, t;
    for (s = 0; s <= (steps || 56); s++) { t = (s / (steps || 56)) * 2 * Math.PI; pts.push(project3D(cam, radius * Math.cos(t), y0 || 0, radius * Math.sin(t))); }
    return pts; }
var LIVE_EDGE_TOL = 2;
function resolveVisibleWindow() {
    var bars = null, srcTag = 'FALLBACK';
    try {
        if (i_visRange && typeof i_visRange.candleIndexFrom === 'number' && typeof i_visRange.candleIndexTo === 'number' && i_visRange.candleIndexTo > i_visRange.candleIndexFrom) {
            var start = i_visRange.candleIndexFrom, end = i_visRange.candleIndexTo;
            var live = (n - 1 - end) <= LIVE_EDGE_TOL;
            if (live) end = n - 1;
            start = clamp(Math.floor(start), 0, n - 1);
            end = clamp(Math.ceil(end), start + 1, n - 1);
            bars = Math.max(1, end - start);
            srcTag = 'ANCHOR_VISIBLE_RANGE'; }
    } catch (eVr) { bars = null; }
    if (bars == null || !isFinite(bars) || bars <= 0) { bars = SURF.lookback; srcTag = 'FALLBACK'; }
    bars = clamp(bars, 15, 3000);
    var minBars = 25, maxBars = 1500;
    var loB = Math.log(minBars), hiB = Math.log(maxBars);
    var zoomT = clamp01(safeDiv(Math.log(clamp(bars, minBars, maxBars)) - loB, hiB - loB, 0));
    var label = zoomT < 0.33 ? 'TIGHT' : zoomT < 0.66 ? 'STANDARD' : 'WIDE';
    return {
        bars: bars, zoomT: zoomT, label: label, sourceTag: srcTag,
        strikeWindowMult: clamp(lerp(0.65, 1.85, zoomT), 0.60, 1.90),
        rowCountAdj: clamp(lerp(0.85, 1.25, zoomT), 0.80, 1.30),
        stepMult: clamp(lerp(0.70, 2.20, zoomT), 0.65, 2.30) };}
var CASCADE = resolveVisibleWindow();
// ============================================================================
// ENTROPY
// ============================================================================
var upBars = make(n, 0);
var entropy = make(n, 0);
var hlcc4_wma = wma(hlcc4, 3);
for (i = 1; i < n; i++) upBars[i] = (hlcc4_wma[i] > hlcc4_wma[i - 1]) ? 1 : 0;
var pUpSeries = sma(upBars, adaptivePeriod);
for (i = 0; i < n; i++) {
  var pE = isNaN(pUpSeries[i]) ? 0.5 : pUpSeries[i]; entropy[i] = (pE > 0 && pE < 1) ? (-pE * Math.log(pE) - (1 - pE) * Math.log(1 - pE)) / Math.log(2) : 0;}
function makeRollingPercentile(maxLen) {
  var sorted = [], queue = [];
  return function push(value) {
    var insertIdx = bsBounds.ge(sorted, value); sorted.splice(insertIdx, 0, value); queue.push(value);
    var removedIdx = -1;
    if (queue.length > maxLen) { var old = queue.shift(); removedIdx = bsBounds.eq(sorted, old); if (removedIdx >= 0) sorted.splice(removedIdx, 1); }
    var rankIdx = insertIdx;
    if (removedIdx >= 0 && removedIdx < insertIdx) rankIdx -= 1;
    var len = sorted.length;
    return len > 1 ? (rankIdx / (len - 1)) * 100 : 50;};}
var entropyPercentileArr = make(n, 50);
var entropyCalibLookback = 200;
{ var tracker = makeRollingPercentile(entropyCalibLookback);
  for (i = 0; i < n; i++) entropyPercentileArr[i] = isNaN(entropy[i]) ? 50 : tracker(entropy[i]);}
var entropyPercentile = 40;
var effEntropyPercentile = make(n, 40);
for (i = 0; i < n; i++) effEntropyPercentile[i] = entropyPercentile;
// ============================================================================
// TREND + VOLUME + FRACTAL + PIVOT
// ============================================================================
var trendUp = make(n, false), trendDown = make(n, false);
for (i = 0; i < n; i++) { trendUp[i] = close[i] > ema21[i]; trendDown[i] = close[i] < ema21[i]; }
var volAvg = sma(volume, 20);
var enableVol = true;
var volMultiplier = 1.5;
var volSpike = make(n, false);
for (i = 0; i < n; i++) volSpike[i] = enableVol ? (volume[i] > volMultiplier * (volAvg[i] > 0 ? volAvg[i] : 1)) : true;
var fractalHigh = make(n, false), fractalLow = make(n, false);
for (i = 2; i < n - 2; i++) {
  fractalHigh[i] = high[i] > high[i - 1] && high[i] > high[i + 1] && high[i - 1] > high[i - 2] && high[i + 1] > high[i + 2];
  fractalLow[i]  = low[i]  < low[i - 1]  && low[i]  < low[i + 1]  && low[i - 1] < low[i - 2]  && low[i + 1] < low[i + 2];}
var pivotHigh = make(n, false), pivotLow = make(n, false);
for (i = 2; i < n; i++) {
  pivotHigh[i] = high[i - 1] > high[i - 2] && high[i - 1] > high[i];
  pivotLow[i]  = low[i - 1]  < low[i - 2]  && low[i - 1]  < low[i];}
// ============================================================================
// WICK PRESSURE KERNEL (WPK)
// ============================================================================
var WPK = make(n, 0);
for (i = 0; i < n; i++) {
  var sup = 0.0, dem = 0.0;
  var t0 = Math.max(0, i - 30 + 1);
  for (var jW = t0; jW <= i; jW++) {
    var ageW2 = i - jW;
    var wAge = Math.exp(-ageW2 / 60);
    var aW2 = atr14[jW] || 1e-8;
    var dUp = Math.exp(-Math.abs(close[i] - high[jW]) / (1.5 * aW2));
    var dDn = Math.exp(-Math.abs(close[i] - low[jW]) / (1.5 * aW2));
    var jBodyTop = Math.max(open[jW], close[jW]);
    var jBodyBot = Math.min(open[jW], close[jW]);
    var jUpper = Math.max(0, high[jW] - jBodyTop) / Math.max(1e-8, high[jW] - low[jW]);
    var jLower = Math.max(0, jBodyBot - low[jW]) / Math.max(1e-8, high[jW] - low[jW]);
    sup += wAge * dUp * jUpper; dem += wAge * dDn * jLower;}
  WPK[i] = dem - sup;}
var meanW = sma(WPK, adaptivePeriod);
var devW = stdev(WPK, adaptivePeriod);
var WPKz = make(n, 0);
for (i = 0; i < n; i++) WPKz[i] = (devW[i] > 0) ? (WPK[i] - meanW[i]) / devW[i] : 0;
// ============================================================================
// CONFIDENCE LOGIC
// ============================================================================
var i_tradeMode = 'Hybrid';
var i_predictiveMode = true;
var i_enableLiveSignals = true;
var i_confirmAtr = 0.25;
var eagerEntry = true;
var adaptiveEngineOn = true;
var adaptiveMemory = 20;
var adaptiveFeedbackClamp = 10;
var adaptiveEvalHorizon = 12;
var effThresholdPeak = 55, effThresholdValley = 55;
var i_enableSpectral = true;
var spectralOrderArr = make(n, 0.5);
var lowestLowPrior = make(n, null), highestHighPrior = make(n, null);
for (i = 1; i < n; i++) {
  var loW = [], hiW = [];
  for (var jP = Math.max(0, i - adaptivePeriod); jP < i; jP++) { loW.push(low[jP]); hiW.push(high[jP]); }
  lowestLowPrior[i] = loW.length ? Math.min.apply(null, loW) : null;
  highestHighPrior[i] = hiW.length ? Math.max.apply(null, hiW) : null;}
var peakSignals = make(n, null), valleySignals = make(n, null);
var peakProbs = make(n, null), valleyProbs = make(n, null);
var peakTentative = make(n, false), valleyTentative = make(n, false);
var peakSignalSource = make(n, null), valleySignalSource = make(n, null);
var peakOutcomeQueue = [], valleyOutcomeQueue = [];
var recentPeakResults = [], recentValleyResults = [];
var peakWinRate = 0.5, valleyWinRate = 0.5;
var peakAdaptiveAdj = 0, valleyAdaptiveAdj = 0;
var shortConf = make(n, false);
var lastSignalType = 'None';
var lastPeakPrice = null, lastPeakVel = null;
var lastValleyPrice = null, lastValleyVel = null;
var lastPeakSignalBar = -999, lastValleySignalBar = -999;
function pushCapped(arr, val, cap) { arr.push(val); if (arr.length > cap) arr.shift(); }
function rollingWinRate(arr) { if (!arr.length) return 0.5; var s = 0; for (var q = 0; q < arr.length; q++) if (arr[q]) s++; return s / arr.length; }
var sigStart = Math.max(adaptivePeriod + 10, 5);
for (i = sigStart; i < n; i++) {
  if (isNaN(entropy[i])) continue;
  if (adaptiveEngineOn) {
    while (peakOutcomeQueue.length && peakOutcomeQueue[0].resolveAt <= i) {
      var pendP = peakOutcomeQueue.shift();
      pushCapped(recentPeakResults, close[i] < pendP.price, adaptiveMemory);}
    while (valleyOutcomeQueue.length && valleyOutcomeQueue[0].resolveAt <= i) {
      var pendV = valleyOutcomeQueue.shift();
      pushCapped(recentValleyResults, close[i] > pendV.price, adaptiveMemory);}
    peakWinRate   = rollingWinRate(recentPeakResults);
    valleyWinRate = rollingWinRate(recentValleyResults);
    peakAdaptiveAdj   = clamp((0.5 - peakWinRate) * 30, -adaptiveFeedbackClamp, adaptiveFeedbackClamp);
    valleyAdaptiveAdj = clamp((0.5 - valleyWinRate) * 30, -adaptiveFeedbackClamp, adaptiveFeedbackClamp);}
  else { peakAdaptiveAdj = 0; valleyAdaptiveAdj = 0; }
  var isPeakEvent   = pivotHigh[i];
  var isValleyEvent = pivotLow[i];
  var atrHere = atr14[i] || 1e-8;
  var buf = i_confirmAtr * atrHere;
  var statePeak   = high[i] >= highest(high, adaptivePeriod)[i] - buf;
  var stateValley = low[i]  <= lowest(low, adaptivePeriod)[i] + buf;
  var bearDiv = isPeakEvent   && lastPeakPrice   != null && high[i] > lastPeakPrice   && velocity[i] < lastPeakVel;
  var bullDiv = isValleyEvent && lastValleyPrice != null && low[i]  < lastValleyPrice && velocity[i] > lastValleyVel;
  var enoughDistPeak   = (i - lastPeakSignalBar)   >= minSignalDistance;
  var enoughDistValley = (i - lastValleySignalBar) >= minSignalDistance;
  var wpkVal = WPKz[i] || 0;
  var trendStrNorm = Math.abs(velocity[i] || 0) / atrHere;
  var accNorm = (acceleration[i] || 0) / atrHere;
  var barRangeNorm = atrHere > 0 ? (high[i] - low[i]) / atrHere : 1;
  var avgVolRatio = volAvg[i] > 0 ? volume[i] / volAvg[i] : 1;
  var entropyOk = entropyPercentileArr[i] <= effEntropyPercentile[i];
  var entropyGatePeak = eagerEntry ? true : entropyOk;
  var entropyGateValley = eagerEntry ? true : entropyOk;
  var qualityBoostPeak = eagerEntry ? (1 + (entropyOk ? 0.15 : 0)) : 1;
  var qualityBoostValley = eagerEntry ? (1 + (entropyOk ? 0.15 : 0)) : 1;
  var trendIsUp = trendUp[i], trendIsDown = trendDown[i];
  var peakCondition1 = (i_tradeMode === 'Reversal' || i_tradeMode === 'Hybrid') && isPeakEvent && close[i] < low[i - 1] - buf && trendIsUp && entropyGatePeak;
  var peakCondition2 = (i_tradeMode === 'Breakout' || i_tradeMode === 'Hybrid') && lowestLowPrior[i] != null && close[i] < lowestLowPrior[i] && volSpike[i];
  var peakConditionPredictive = i_predictiveMode && acceleration[i] < 0 && statePeak && entropyGatePeak;
  if ((peakCondition1 || peakCondition2 || peakConditionPredictive) && enoughDistPeak) {
    var orderlinessScore = clamp(24 * (1 - entropyPercentileArr[i] / 100), 0, 24);
    var trendScore = clamp((trendStrNorm / 2.5) * 20, 0, 20);
    var divScore = bearDiv ? clamp(16 + Math.abs(accNorm) * 8, 16, 22) : 0;
    var volScore = volSpike[i] ? clamp(6 + (avgVolRatio - 1) * 4, 6, 12) : 0;
    var wpkConfidence = jStat.normal.cdf(Math.abs(Math.max(0, -wpkVal)), 0, 1) - 0.5;
    var wpkScoreVal = clamp(wpkConfidence * 2 * 12, 0, 15);
    var adaptiveAdj = clamp(peakAdaptiveAdj || 0, -adaptiveFeedbackClamp, adaptiveFeedbackClamp);
    var variation = clamp((barRangeNorm - 1) * 3, -3, 3);
    var spectralBonus = i_enableSpectral ? clamp((spectralOrderArr[i] - 0.5) * 10, -5, 5) : 0;
    var rawP = 34 + orderlinessScore + trendScore + divScore + volScore + wpkScoreVal + adaptiveAdj + variation + spectralBonus; rawP *= (qualityBoostPeak || 1);
    var probP = clamp(Math.round(rawP), 0, 99);
    if (probP >= effThresholdPeak) {
      var tentativeP = i_enableLiveSignals && i === n - 1;
      var sourceP;
      if (peakCondition2) sourceP = 'breakout';
      else if (peakConditionPredictive) sourceP = 'predictive';
      else if (peakCondition1 && pivotHigh[i - 1]) sourceP = 'pivot';
      else sourceP = 'fractal';
      peakSignals[i] = high[i]; peakProbs[i] = probP; peakTentative[i] = tentativeP;
      peakSignalSource[i] = sourceP;
      lastPeakSignalBar = i; lastSignalType = 'Peak';
      if (adaptiveEngineOn && !tentativeP) peakOutcomeQueue.push({ price: high[i], resolveAt: i + adaptiveEvalHorizon });}}
  if (isPeakEvent) { lastPeakPrice = high[i]; lastPeakVel = velocity[i]; }
  var valleyCondition1 = (i_tradeMode === 'Reversal' || i_tradeMode === 'Hybrid') && isValleyEvent && close[i] > high[i - 1] + buf && trendIsDown && entropyGateValley;
  var valleyCondition2 = (i_tradeMode === 'Breakout' || i_tradeMode === 'Hybrid') && highestHighPrior[i] != null && close[i] > highestHighPrior[i] && volSpike[i];
  var valleyConditionPredictive = i_predictiveMode && acceleration[i] > 0 && stateValley && entropyGateValley;
  if ((valleyCondition1 || valleyCondition2 || valleyConditionPredictive) && enoughDistValley) {
    var orderlinessScore2 = clamp(24 * (1 - entropyPercentileArr[i] / 100), 0, 24);
    var trendScore2 = clamp((trendStrNorm / 2.5) * 20, 0, 20);
    var divScore2 = bullDiv ? clamp(16 + Math.abs(accNorm) * 8, 16, 22) : 0;
    var volScore2 = volSpike[i] ? clamp(6 + (avgVolRatio - 1) * 4, 6, 12) : 0;
    var wpkConfidence2 = jStat.normal.cdf(Math.abs(Math.max(0, wpkVal)), 0, 1) - 0.5;
    var wpkScoreVal2 = clamp(wpkConfidence2 * 2 * 12, 0, 15);
    var adaptiveAdj2 = clamp(valleyAdaptiveAdj || 0, -adaptiveFeedbackClamp, adaptiveFeedbackClamp);
    var spectralBonus2 = i_enableSpectral ? clamp((spectralOrderArr[i] - 0.5) * 10, -5, 5) : 0;
    var rawV = 34 + orderlinessScore2 + trendScore2 + divScore2 + volScore2 + wpkScoreVal2 + adaptiveAdj2 + variation + spectralBonus2; rawV *= (qualityBoostValley || 1);
    var probV = clamp(Math.round(rawV), 0, 99);
    if (probV >= effThresholdValley) {
      var tentativeV = i_enableLiveSignals && i === n - 1;
      var sourceV;
      if (valleyCondition2) sourceV = 'breakout';
      else if (valleyConditionPredictive) sourceV = 'predictive';
      else if (valleyCondition1 && pivotLow[i - 1]) sourceV = 'pivot';
      else sourceV = 'fractal';
      valleySignals[i] = low[i]; valleyProbs[i] = probV; valleyTentative[i] = tentativeV;
      valleySignalSource[i] = sourceV;
      lastValleySignalBar = i; lastSignalType = 'Valley';
      if (adaptiveEngineOn && !tentativeV) valleyOutcomeQueue.push({ price: low[i], resolveAt: i + adaptiveEvalHorizon });}}
  if (isValleyEvent) { lastValleyPrice = low[i]; lastValleyVel = velocity[i]; }}
// ============================================================================
// ADVANCED SYNTHETIC MODEL
// ============================================================================
var currentPhase = 'FLOW';
var synPCRLast = 1.0;
var relVolArr = make(n, 1);
for (i = 0; i < n; i++) relVolArr[i] = (volAvg[i] > 0) ? (volume[i] || 0) / volAvg[i] : 1;
function fillSyntheticSurface(widen, oiScale) {
    var spot = close[lastIdx], atrV = Math.max(nz(atr14[lastIdx], 1), EPS);
    var tBias = ema21[lastIdx] >= ema55[lastIdx] ? 1 : -1;
    var phaseBias = currentPhase === 'FLOW' ? 0.18 : currentPhase === 'EXPANSION' ? -0.14 : 0.00;
    var pcrBias = clamp((synPCRLast - 1) / 1.4, -0.65, 0.65), volBias = clamp01(relVolArr[lastIdx] / 2.5), memBias = clamp01(SYN.memory);
    var winSR = localPriceWindow(spot, atrV);
    var strikeRange = Math.max((winSR.hi - winSR.lo) / 2, atrV * 3);
    var strikeStep = inferPriceStep(spot, atrV, strikeRange * 2);
    var strikeLo = roundToStep(spot - strikeRange, strikeStep);
    var strikeHi = roundToStep(spot + strikeRange, strikeStep);
    var roundStep = niceNum(Math.max(strikeStep * 5, spot * 0.002));
    var hiLook = -Infinity, loLook = Infinity, i6;
    for (i6 = Math.max(0, n - SURF.lookback); i6 < n; i6++) { if (high[i6] > hiLook) hiLook = high[i6]; if (low[i6] < loLook) loLook = low[i6]; }
    var anchors = [];
    function addAnchor(p, w, s) {
      if (isFinite(p) && p >= strikeLo - strikeStep && p <= strikeHi + strikeStep)
        anchors.push({ p: p, w: w, s: Math.max(s || 1, EPS) }); }
    var sigmaBase = Math.max(atrV * 1.8, strikeStep * 2.5, 1) * Math.max(widen, 0.6);
    var aggr = clamp(SYN.aggression, 0.80, 2.50);
    addAnchor(spot, 1.45 * aggr, sigmaBase * 0.90 * memBias);
    addAnchor(vwapArr[lastIdx], 1.20 * aggr, sigmaBase * 1.10);
    addAnchor(ema21[lastIdx], 1.00 * aggr, sigmaBase * 1.18);
    addAnchor(ema55[lastIdx], 0.92 * aggr, sigmaBase * 1.32);
    addAnchor(ema200[lastIdx], 0.72 * aggr, sigmaBase * 1.45);
    addAnchor(hiLook, 0.75 * aggr, sigmaBase * 1.20);
    addAnchor(loLook, 0.75 * aggr, sigmaBase * 1.20);
    addAnchor(open[lastIdx], 0.78 * aggr, sigmaBase * 1.05);
    addAnchor(spot + atrV * 2, 0.60 * aggr, sigmaBase * 1.05);
    addAnchor(spot - atrV * 2, 0.60 * aggr, sigmaBase * 1.05);
    var roundBase = roundToStep(spot, roundStep);
    addAnchor(roundBase, 1.20 * aggr, sigmaBase * 0.95);
    addAnchor(roundBase + roundStep, 0.68 * aggr, sigmaBase * 0.95);
    addAnchor(roundBase - roundStep, 0.68 * aggr, sigmaBase * 0.95);
    var surface = {}, kWalk;
    for (kWalk = strikeLo; kWalk <= strikeHi + EPS; kWalk += strikeStep) {
        var K = Math.round(kWalk * 100) / 100, base = 0, ai;
        for (ai = 0; ai < anchors.length; ai++) base += anchors[ai].w * gaussian(K, anchors[ai].p, anchors[ai].s);
        var roundMag = gaussian(K, roundToStep(K, roundStep), strikeStep * 1.20);
        var centerMag = gaussian(K, spot + tBias * atrV * 0.10, sigmaBase * 0.95);
        var sessionPulse = gaussian(K, hiLook, sigmaBase * 1.20) + gaussian(K, loLook, sigmaBase * 1.20);
        var wave = 1 + 0.05 * Math.sin((K - spot) / Math.max(strikeStep, 1) * 1.6 + tBias * 0.75) + 0.03 * Math.cos((K - spot) / Math.max(strikeStep, 1) * 0.9);
        var pressureSeed = (base * 0.66 + centerMag * 0.18 + roundMag * 0.10 + sessionPulse * 0.06); pressureSeed = Math.pow(Math.max(pressureSeed, EPS), 0.88) * wave * aggr * (1 + volBias * 0.28) * oiScale;
        var above = K >= spot ? 1 : 0;
        var callTilt = clamp(0.48 + 0.20 * above + 0.10 * tBias + 0.08 * phaseBias - 0.10 * pcrBias + 0.05 * volBias, 0.12, 0.92);
        var putTilt = clamp(0.52 + 0.20 * (1 - above) - 0.10 * tBias - 0.08 * phaseBias + 0.10 * pcrBias + 0.04 * (1 - volBias), 0.12, 0.92);
        var callOI = pressureSeed * 10000 * callTilt, putOI = pressureSeed * 10000 * putTilt; surface[K] = { callOI: callOI, putOI: putOI, totalOI: callOI + putOI }; }
    return surface;}
function buildAdvancedSyntheticModel() {
    var nExp = Math.max(3, Math.min(SURF.numExp, 8)), slices = [], surface = {}, e, ki, K;
    for (e = 0; e < nExp; e++) {
        var widen = 1 + e * 0.32, oiScale = 1 - e * 0.09;
        var sliceSurf = fillSyntheticSurface(widen, Math.max(oiScale, 0.35));
        var totOI = 0, keys = Object.keys(sliceSurf);
        for (ki = 0; ki < keys.length; ki++) totOI += sliceSurf[keys[ki]].totalOI; slices.push({ dte: 7 * (e + 1), label: (7 * (e + 1)) + 'D', surface: sliceSurf, totalOI: totOI });
        for (ki = 0; ki < keys.length; ki++) { K = Number(keys[ki]);
            if (!surface[K]) surface[K] = { callOI: 0, putOI: 0, totalOI: 0 }; surface[K].callOI += sliceSurf[K].callOI; surface[K].putOI += sliceSurf[K].putOI; surface[K].totalOI += sliceSurf[K].totalOI; } }
    return finalizeStrikeSurface(surface, { sourceTag: 'SHADOW', sourceText: 'ADVANCED SYNTHETIC', note: 'Synthetic tenor surface — live chain unavailable', slices: slices }, close[lastIdx], nz(atr14[lastIdx], 1));}
// ============================================================================
// LIVE OPTIONS MODEL
// ============================================================================
async function buildLiveOptionsModel() {
  var surface  = {}, slices = [];
  var schedule = null;
  try { schedule = await request.options_schedule(constants.ticker); }
  catch (e) { return { ok: false, error: String(e) }; }
  if (!schedule || schedule.length === 0) return { ok: false, error: 'No options schedule available' };
  var sorted = schedule.slice(0).sort(function (a, b) {
    return nz(a && a.expiration && a.expiration.dte, 0) - nz(b && b.expiration && b.expiration.dte, 0);});
  // request budget: 1 options_schedule + ≤ REQ_BUDGET_O − 1 per-expiry chains — the platform allows 6 "other" request.* calls per script, so the cap is explicit, not implied by the input list
  var REQ_BUDGET_O = 6, targets = sorted.slice(0, Math.max(0, Math.min(numExp, REQ_BUDGET_O - 1, sorted.length)));
  var liveExpCount = 0, failedExp = 0;
  var aggCallOI = 0, aggPutOI = 0;
  var sliceSurfaces = [];
  // all expirations are requested in parallel (one round-trip instead of numExp sequential ones); a failed expiry resolves to null and is skipped exactly as before
  var jobs = [], ti;
  for (ti = 0; ti < targets.length; ti++) { (function (expInfo) {
    var pr; if (!expInfo || !expInfo.code) pr = Promise.resolve(null); else { try { pr = Promise.resolve(request.options_data_for_expiration(constants.ticker, expInfo.code, ['oi', 'oic', 'dvol'])); } catch (eSync) { pr = Promise.resolve(null); } }
    jobs.push(pr.then(function (d) { return d; }, function (eExp) { return null; })); })(targets[ti] && targets[ti].expiration); }
  var chains = await Promise.all(jobs);
  for (ti = 0; ti < targets.length; ti++) {
    var expInfo = targets[ti] && targets[ti].expiration;
    if (!expInfo || !expInfo.code) continue;
    var chainData = chains[ti];
    if (!chainData || !chainData.resultByStrike) { failedExp++; continue; } liveExpCount++;
    var keys = Object.keys(chainData.resultByStrike);
    var sliceSurf = {};
    var sCall = 0, sPut = 0;
    for (var ki = 0; ki < keys.length; ki++) {
      var K = Number(keys[ki]);
      if (!isFinite(K)) continue;
      var entry = chainData.resultByStrike[keys[ki]];
      var callOI = nz(entry && entry.C && entry.C.oi, 0);
      var putOI  = nz(entry && entry.P && entry.P.oi, 0);
      var callOic = nz(entry && entry.C && entry.C.oic, 0);
      var putOic  = nz(entry && entry.P && entry.P.oic, 0);
      var callDvol = nz(entry && entry.C && entry.C.dvol, 0);
      var putDvol  = nz(entry && entry.P && entry.P.dvol, 0);
      sliceSurf[K] = { callOI: callOI, putOI: putOI, totalOI: callOI + putOI, callOIC: callOic, putOIC: putOic, callDVol: callDvol, putDVol: putDvol };
      if (!surface[K]) surface[K] = { callOI: 0, putOI: 0, totalOI: 0, callOic: 0, putOic: 0, dvol: 0 };
      surface[K].callOI  += callOI;
      surface[K].putOI   += putOI;
      surface[K].totalOI += callOI + putOI;
      surface[K].callOic += callOic;
      surface[K].putOic  += putOic;
      surface[K].dvol    += callDvol + putDvol;
      aggCallOI += callOI;
      aggPutOI  += putOI; sCall += callOI; sPut += putOI;}
    slices.push({ dte: nz(expInfo.dte, ti * 7), label: (nz(expInfo.dte, ti * 7) | 0) + 'D', surface: sliceSurf, totalOI: sCall + sPut }); }
  if (Object.keys(surface).length === 0) return { ok: false, error: 'Live chain fetched but empty' };
  var model = finalizeStrikeSurface(surface, {
    sourceTag: 'LIVE',
    sourceText: 'LIVE OPTIONS',
    note: liveExpCount + ' expirations loaded' + (failedExp ? ' · ' + failedExp + ' failed' : ''),
    slices: slices
  }, close[lastIdx], nz(atr14[lastIdx], 1));
  model.liveExpCount = liveExpCount; model.ok = true;
  return model;}
// ============================================================================
// SLF MODEL
// ============================================================================
function histogramSlice(start, end, pLo, rowH, rowCount) {
    var rowBuy = make(rowCount, 0), rowSell = make(rowCount, 0), rowTotal = make(rowCount, 0), i4, r;
    for (i4 = start; i4 < end; i4++) {
        var loRow = clamp(Math.floor((low[i4] - pLo) / rowH), 0, rowCount - 1);
        var hiRow = clamp(Math.floor((high[i4] - pLo) / rowH), 0, rowCount - 1);
        var span = Math.max(1, hiRow - loRow + 1), v = nz(volume[i4], 0) / span;
        var buyFrac = clamp01((deltaProxyArr[i4] + 1) / 2), buyV = v * buyFrac, sellV = v * (1 - buyFrac);
        for (r = loRow; r <= hiRow; r++) { rowBuy[r] += buyV; rowSell[r] += sellV; rowTotal[r] += v; } }
    var surface = {};
    for (r = 0; r < rowCount; r++) { var px = pLo + (r + 0.5) * rowH; surface[px] = { callOI: rowBuy[r], putOI: rowSell[r], totalOI: rowTotal[r] }; }
    return { surface: surface, rowBuy: rowBuy, rowSell: rowSell, rowTotal: rowTotal };}
function buildProfileModel() {
  var lookback = Math.min(300, n);
  var start = Math.max(0, n - lookback);
  var pHi = -Infinity, pLo = Infinity;
  for (var i = start; i < n; i++) { if (high[i] > pHi) pHi = high[i]; if (low[i] < pLo) pLo = low[i]; }
  if (!isFinite(pHi) || !isFinite(pLo)) { pHi = close[lastIdx]; pLo = close[lastIdx]; }
  var step = inferPriceStep(close[lastIdx], nz(atr14[lastIdx], 1), pHi - pLo);
  pLo = Math.floor(pLo / step) * step;
  pHi = Math.ceil(pHi / step) * step;
  if (pHi <= pLo) pHi = pLo + step * 8;
  var rowH = inferPriceStep(close[lastIdx], nz(atr14[lastIdx], 1), pHi - pLo);
  if (rowH < step) rowH = step;
  rowH = Math.max(step, Math.round(rowH / step) * step);
  var rowCount = clamp(Math.round((pHi - pLo) / rowH), 8, 36);
  rowH = (pHi - pLo) / Math.max(rowCount, 1);
  PRICE_STEP = step;
  var nSlices = Math.max(3, Math.min(SURF.numExp, 8)), sliceBars = Math.max(8, Math.floor(Math.min(SURF.lookback, n) / nSlices));
  var slices = [], s;
  for (s = 0; s < nSlices; s++) {
    var sEnd = n - (nSlices - 1 - s) * sliceBars, sStart = Math.max(0, sEnd - sliceBars);
    var hist = histogramSlice(sStart, sEnd, pLo, rowH, rowCount);
    slices.push({ dte: (nSlices - 1 - s) * sliceBars, label: s === nSlices - 1 ? 'NOW' : 'T-' + (nSlices - 1 - s), surface: hist.surface, totalOI: hist.rowTotal.reduce(function(a,b){return a+b;}, 0) }); }
  var rowPrice = make(rowCount, 0), rowBuy = make(rowCount, 0), rowSell = make(rowCount, 0), rowTotal = make(rowCount, 0), rowDelta = make(rowCount, 0);
  for (var r = 0; r < rowCount; r++) rowPrice[r] = roundToStep(pLo + (r + 0.5) * rowH, step);
  for (i = start; i < n; i++) {
    var loRow = clamp(Math.floor((low[i] - pLo) / rowH), 0, rowCount - 1);
    var hiRow = clamp(Math.floor((high[i] - pLo) / rowH), 0, rowCount - 1);
    var span = Math.max(1, hiRow - loRow + 1);
    var v = nz(volume[i], 0) / span;
    var barSpan = high[i] - low[i];
    var buyFrac = barSpan > EPS ? clamp01((close[i] - low[i]) / barSpan) : 0.5;
    var sellFrac = 1 - buyFrac;
    var buyV = v * buyFrac, sellV = v * sellFrac;
    for (var r2 = loRow; r2 <= hiRow; r2++) { rowBuy[r2] += buyV; rowSell[r2] += sellFrac > 0 ? sellV : 0; rowTotal[r2] += v; rowDelta[r2] += buyV - sellV; }}
  var totalVol = 0, totalBuy = 0, totalSell = 0;
  for (r = 0; r < rowCount; r++) { totalVol += rowTotal[r]; totalBuy += rowBuy[r]; totalSell += rowSell[r]; }
  var pocRow = 0, pocMax = -Infinity;
  for (r = 0; r < rowCount; r++) if (rowTotal[r] > pocMax) { pocMax = rowTotal[r]; pocRow = r; }
  var left = pocRow, right = pocRow, acc = rowTotal[pocRow], target = totalVol * 0.70;
  while (acc < target && (left > 0 || right < rowCount - 1)) {
    var lv = left > 0 ? rowTotal[left - 1] : -1, rv = right < rowCount - 1 ? rowTotal[right + 1] : -1;
    if (rv >= lv) { right++; acc += Math.max(rv, 0); } else { left--; acc += Math.max(lv, 0); }}
  var pocPrice = roundToStep(rowPrice[pocRow], step), valPrice = roundToStep(pLo + left * rowH, step), vahPrice = roundToStep(pLo + (right + 1) * rowH, step);
  var curRow = clamp(Math.floor((close[lastIdx] - pLo) / rowH), 0, rowCount - 1);
  var rows = [];
  for (var rr = rowCount - 1; rr >= 0; rr--) {
    var isPOC = rr === pocRow;
    var isVAH = Math.abs(rowPrice[rr] - vahPrice) <= rowH * 0.45;
    var isVAL = Math.abs(rowPrice[rr] - valPrice) <= rowH * 0.45;
    var isCUR = rr === curRow;
    rows.push({ level: rowPrice[rr], label: isPOC ? 'POC' : isVAH ? 'VAH' : isVAL ? 'VAL' : isCUR ? 'CUR' : 'MID', leftVal: rowSell[rr], rightVal: rowBuy[rr], totalVal: rowTotal[rr], deltaVal: rowDelta[rr], isATM: false, isPOC: isPOC, isVAH: isVAH, isVAL: isVAL, isCUR: isCUR });}
  var refsA = [], refsB = [], refsT = [], refsD = [];
  for (i = 0; i < rows.length; i++) { refsA.push(rows[i].leftVal); refsB.push(rows[i].rightVal); refsT.push(rows[i].totalVal); refsD.push(Math.abs(rows[i].deltaVal)); }
  return { ok: true, kind: 'SLF', sourceTag: 'SLF', sourceText: 'SLF', note: 'Price profile mode',
    currentPrice: close[lastIdx], pcr: totalBuy > 0 ? totalSell / totalBuy : 99,
    bias: safeDiv(totalBuy - totalSell, totalBuy + totalSell, 0),
    leftName: 'SELLS', rightName: 'BUYS', surfaceCount: rowCount, rowCount: rows.length, rows: rows,
    slices: slices,
    pocStrike: pocPrice, vahStrike: vahPrice, valStrike: valPrice, curStrike: close[lastIdx],
    rails: [{ name: 'POC', value: pocPrice, color: T.gold, label: 'POC ' + fmtPrice(pocPrice) },
            { name: 'VAH', value: vahPrice, color: T.bull, label: 'VAH ' + fmtPrice(vahPrice) },
            { name: 'VAL', value: valPrice, color: T.bear, label: 'VAL ' + fmtPrice(valPrice) }],
    leftTotal: totalSell, rightTotal: totalBuy, allTotal: totalVol, refs: { leftRef: percentile(refsA, 0.80), rightRef: percentile(refsB, 0.80), totalRef: percentile(refsT, 0.80), deltaRef: percentile(refsD, 0.80) } };}
var deltaProxy = 0;
function analyzeSurface(mdl, atrV, spot) {
    var lab = { cog: spot, cogATR: 0, skew: 0, kurt: 0, entropy: 1, putCallR: 0, pocZ: 0, structure: 0, specBars: '··········', domPeriodATR: 0, cycleBars: 0, cyclePhase: CLOCK.phase12, cycleConf: 0 };
    var rows = mdl.rows || [];
    if (!rows.length) return lab;
    var mass = [], lefts = [], rights = [], levels = [], wsum = 0, wlev = 0, i7;
    for (i7 = 0; i7 < rows.length; i7++) { mass.push(nz(rows[i7].totalVal, 0)); lefts.push(nz(rows[i7].leftVal, 0)); rights.push(nz(rows[i7].rightVal, 0)); levels.push(rows[i7].level); wsum += nz(rows[i7].totalVal, 0); wlev += rows[i7].level * nz(rows[i7].totalVal, 0); }
    lab.cog = wsum > EPS ? wlev / wsum : spot; lab.cogATR = (lab.cog - spot) / Math.max(atrV, EPS); lab.skew = QC.skewness(mass); lab.kurt = QC.kurtosis(mass); lab.entropy = QC.shannonH(mass); lab.putCallR = QC.corrcoeff(lefts, rights);
    var mu = QC.mean(mass), sd = QC.stdev(mass, true), pocMass = 0, ri;
    for (ri = 0; ri < rows.length; ri++) if (rows[ri].isPOC) pocMass = nz(rows[ri].totalVal, 0);
    lab.pocZ = sd > EPS ? (pocMass - mu) / sd : 0;
    try {
        var win = mass.slice(0), wi;
        for (wi = 0; wi < win.length; wi++) win[wi] = win[wi] * QC.hann(win.length, wi);
        var sp = QC.spectrum(win), mags = sp.mag, half = Math.floor(mags.length / 2);
        var band = [], bi, totB = 0, peakB = 0, peakI = 1;
        for (bi = 1; bi < half; bi++) { band.push(mags[bi]); totB += mags[bi]; if (mags[bi] > peakB) { peakB = mags[bi]; peakI = bi; } }
        lab.structure = totB > EPS ? peakB / totB : 0;
        lab.specBars = specGlyph(band.slice(0, 10));
        var dK = 1;
        if (levels.length > 1) { var diffs = []; for (wi = 1; wi < levels.length; wi++) diffs.push(Math.abs(levels[wi] - levels[wi - 1])); diffs.sort(sortNum); dK = Math.max(diffs[Math.floor(diffs.length * 0.25)] || 1, EPS); }
        lab.domPeriodATR = (mags.length * dK / Math.max(peakI, 1)) / Math.max(atrV, EPS);
    } catch (eSpec) {}
    try {
        var Nfft = 64, rs = [], rsStart = Math.max(1, n - Nfft), ri2;
        for (ri2 = rsStart; ri2 < n; ri2++) rs.push(nz(logRet[ri2], 0) * QC.hann(Math.min(Nfft, n - rsStart), ri2 - rsStart));
        if (rs.length >= 16) {
            var sp2 = QC.spectrum(rs), pm = sp2.mag, phalf = Math.floor(pm.length / 2);
            var pTot = 0, pPeak = 0, pI = 2, pj;
            for (pj = 1; pj < phalf; pj++) { pTot += pm[pj]; if (pj >= 2 && pj <= Math.floor(phalf * 0.35) && pm[pj] > pPeak) { pPeak = pm[pj]; pI = pj; } }
            lab.cycleConf = pTot > EPS ? pPeak / pTot : 0; lab.cycleBars = pm.length / Math.max(pI, 1);
            var angs = sp2.ang; lab.cyclePhase = nz(angs[pI], 0) + (2 * Math.PI * (n % Math.max(lab.cycleBars, 1))) / Math.max(lab.cycleBars, 1);}
    } catch (eCyc) {}
    return lab;}
// ============================================================================
// SMART AUTO RESOLVER
// ============================================================================
async function resolveModel() {
  if (i_sourceMode === 'SLF') return buildProfileModel();
  if (i_sourceMode === 'Advanced Synthetic') return buildAdvancedSyntheticModel();
  if (i_sourceMode === 'Live Options') {
    var live = await buildLiveOptionsModel();
    if (live && live.ok) return live;
    var synth = buildAdvancedSyntheticModel(); synth.note = 'Live Options requested but unavailable → Advanced Synthetic';
    return synth;}
  var profile = detectInstrumentProfile();
  if (profile.isFutures || profile.isIndex || profile.isCrypto || profile.isForex) {
    var slfModel = buildProfileModel(); slfModel.note = 'Auto: ' + profile.reason + ' → SLF selected';
    return slfModel;}
  if (profile.hasOptionsChain) {
    var autoLive = await buildLiveOptionsModel();
    if (autoLive && autoLive.ok) { autoLive.note = 'Auto: EQUITY_OPTIONS_OK → ' + (SESSION.live ? 'Live chain active' : 'Options ' + SESSION.label + ' — last close surface'); return autoLive; }
    var fallbackSynth = buildAdvancedSyntheticModel(); fallbackSynth.note = 'Auto: Live chain unavailable → Advanced Synthetic';
    return fallbackSynth;}
  var safeSLF = buildProfileModel(); safeSLF.note = 'Auto: Unknown instrument → SLF selected';
  return safeSLF;}
// ============================================================================
// BUILD RAILS
// ============================================================================
function buildSmartRails(rows, currentPrice, atrV) {
  var allStrikes = [];
  for (var i = 0; i < rows.length; i++) { var s = rows[i].level; if (isFinite(s)) allStrikes.push(s); }
  allStrikes.sort(sortNum);
  if (!allStrikes.length) return [];
  var strikeStep = 1;
  if (allStrikes.length > 1) {
    var diffs = [];
    for (i = 1; i < allStrikes.length; i++) diffs.push(allStrikes[i] - allStrikes[i - 1]); diffs.sort(sortNum); strikeStep = diffs[Math.floor(diffs.length * 0.25)] || 1;}
  var minSep = Math.max(strikeStep * 0.45, atrV * 0.12);
  var atmStrike = allStrikes[0], atmDist = Infinity;
  for (i = 0; i < allStrikes.length; i++) { var d = Math.abs(allStrikes[i] - currentPrice); if (d < atmDist) { atmDist = d; atmStrike = allStrikes[i]; } }
  var atmTouching = atmDist < minSep;
  var abvFloor = atmTouching ? atmStrike + Math.max(strikeStep * 0.90, minSep) : atmStrike + minSep;
  var abvStrike = null;
  for (i = 0; i < allStrikes.length; i++) if (allStrikes[i] > abvFloor) { abvStrike = allStrikes[i]; break; }
  var blwCeil = atmTouching ? atmStrike - Math.max(strikeStep * 0.90, minSep) : atmStrike - minSep;
  var blwStrike = null;
  for (i = allStrikes.length - 1; i >= 0; i--) if (allStrikes[i] < blwCeil) { blwStrike = allStrikes[i]; break; }
  function abvLabel(s) { if (s === null) return 'ABV —'; return 'ABV +' + ((s - currentPrice) / Math.max(atrV, EPS)).toFixed(1) + ' ATR'; }
  function blwLabel(s) { if (s === null) return 'BLW —'; return 'BLW ' + ((s - currentPrice) / Math.max(atrV, EPS)).toFixed(1) + ' ATR'; }
  return [
    { name: 'ABV', value: abvStrike, color: T.bear, label: abvLabel(abvStrike) },
    { name: 'ATM', value: atmStrike, color: T.a1, label: 'ATM ' + fmtPrice(atmStrike) },
    { name: 'BLW', value: blwStrike, color: T.bull, label: blwLabel(blwStrike) }];}
// ============================================================================
// FINALIZE STRIKE SURFACE 
// ============================================================================
function finalizeStrikeSurface(surfaceMap, meta, currentPrice, atrV) {
  var strikes = Object.keys(surfaceMap).map(function (k) { return Number(k); }).filter(function (v) { return isFinite(v); }).sort(sortNum);
  if (!strikes.length) return { ok: false, error: 'Empty strike surface' };
  function cellAt(K) { return surfaceMap[K] || { callOI: 0, putOI: 0, totalOI: 0 }; }
  var stkStep = medianStrikeStep(strikes) || inferPriceStep(currentPrice, atrV, 0); PRICE_STEP = stkStep;
  var win = localPriceWindow(currentPrice, atrV);
  var struct = [];
  var si;
  for (si = 0; si < strikes.length; si++)
    if (strikes[si] >= win.lo && strikes[si] <= win.hi) struct.push(strikes[si]);
  if (struct.length < 9)
    struct = chooseWindow(strikes, currentPrice, Math.min(15, strikes.length)).sort(sortNum);
  if (!struct.length) struct = strikes.slice();
  struct = struct.filter(function (v) { return surfaceMap[v] !== undefined; });
  if (!struct.length) struct = strikes.slice();
  var totalStructOI = 0;
  for (si = 0; si < struct.length; si++) totalStructOI += nz(cellAt(struct[si]).totalOI, 0);
  var pocStrike = struct[0], pocMaxOI = -Infinity;
  for (si = 0; si < struct.length; si++) {
    var tOI = nz(cellAt(struct[si]).totalOI, 0);
    if (tOI > pocMaxOI) { pocMaxOI = tOI; pocStrike = struct[si]; }}
  var pocIdx = struct.indexOf(pocStrike);
  var vaLo = pocIdx, vaHi = pocIdx, vaAcc = nz(cellAt(pocStrike).totalOI, 0);
  var vaTarget = totalStructOI * 0.70;
  while (vaAcc < vaTarget && (vaLo > 0 || vaHi < struct.length - 1)) {
    var loOI = vaLo > 0 ? nz(cellAt(struct[vaLo - 1]).totalOI, 0) : -1;
    var hiOI = vaHi < struct.length - 1 ? nz(cellAt(struct[vaHi + 1]).totalOI, 0) : -1;
    if (hiOI >= loOI) { vaHi++; vaAcc += Math.max(hiOI, 0); } else { vaLo--; vaAcc += Math.max(loOI, 0); }}
  var vahStrike = struct[vaHi], valStrike = struct[vaLo];
  var displayCount = Math.min(30, strikes.length);
  var displayStrikes = chooseWindow(strikes, currentPrice, displayCount); displayStrikes.sort(sortNum).reverse();
  var rows = [];
  var leftTotal = 0, rightTotal = 0, allTotal = 0;
  var curStrike = strikes[0], curDist = Infinity;
  for (si = 0; si < strikes.length; si++) { var dS = Math.abs(strikes[si] - currentPrice); if (dS < curDist) { curDist = dS; curStrike = strikes[si]; } }
  for (i = 0; i < displayStrikes.length; i++) {
    var K = displayStrikes[i];
    var row = surfaceMap[K] || { putOI: 0, callOI: 0, totalOI: 0 };
    var putOI = nz(row.putOI, 0), callOI = nz(row.callOI, 0), totalOI = nz(row.totalOI, putOI + callOI);
    var deltaOI = callOI - putOI;
    var freshOI = nz(row.callOic, 0) + nz(row.putOic, 0) + nz(row.dvol, 0);
    var isPOC = K === pocStrike, isVAH = K === vahStrike, isVAL = K === valStrike, isCUR = K === curStrike;
    rows.push({ level: K, leftVal: putOI, rightVal: callOI, totalVal: totalOI, deltaVal: deltaOI, freshOI: freshOI, isFresh: freshOI > 0, isPOC: isPOC, isVAH: isVAH, isVAL: isVAL, isCUR: isCUR, isATM: false, label: isPOC ? 'POC' : isVAH ? 'VAH' : isVAL ? 'VAL' : isCUR ? 'CUR' : K > currentPrice ? 'ABV' : 'BLW' });
    leftTotal += putOI; rightTotal += callOI; allTotal += totalOI;}
  var smartRails = buildSmartRails(rows, currentPrice, atrV);
  var atmStrike = null;
  for (var ri = 0; ri < smartRails.length; ri++) if (smartRails[ri].name === 'ATM') { atmStrike = smartRails[ri].value; break; }
  for (i = 0; i < rows.length; i++) { rows[i].isATM = rows[i].level === atmStrike; rows[i].label = rows[i].isATM ? 'ATM' : rows[i].label; }
  var refsA = [], refsB = [], refsT = [], refsD = [];
  for (i = 0; i < rows.length; i++) { refsA.push(rows[i].leftVal); refsB.push(rows[i].rightVal); refsT.push(rows[i].totalVal); refsD.push(Math.abs(rows[i].deltaVal)); }
  var oiRails = [
    { name: 'POC', value: pocStrike, color: T.gold, label: 'POC ' + fmtPrice(pocStrike) },
    { name: 'VAH', value: vahStrike, color: T.bull, label: 'VAH ' + fmtPrice(vahStrike) },
    { name: 'VAL', value: valStrike, color: T.bear, label: 'VAL ' + fmtPrice(valStrike) },
    { name: 'CUR', value: currentPrice, color: T.a1, label: 'SPOT ' + fmtPrice(currentPrice) }];
  return { ok: true, kind: 'OPTIONS', sourceTag: meta.sourceTag, sourceText: meta.sourceText, note: meta.note || '',
    currentPrice: currentPrice, pcr: rightTotal > 0 ? leftTotal / rightTotal : 99,
    bias: safeDiv(rightTotal - leftTotal, rightTotal + leftTotal, 0),
    leftName: 'PUTS', rightName: 'CALLS',
    surfaceCount: strikes.length, rowCount: rows.length, rows: rows,
    rails: oiRails, pocStrike: pocStrike, vahStrike: vahStrike, valStrike: valStrike, curStrike: curStrike,
    slices: meta.slices || [],
    leftTotal: leftTotal, rightTotal: rightTotal, allTotal: allTotal,
    refs: { leftRef: percentile(refsA, 0.80), rightRef: percentile(refsB, 0.80), totalRef: percentile(refsT, 0.80), deltaRef: percentile(refsD, 0.80) }};}
// ============================================================================
// RESOLVE
// ============================================================================
var instrProfile = detectInstrumentProfile();
var model = await resolveModel();
if (!model || !model.ok) { model = buildProfileModel(); model.note = 'Emergency SLF fallback'; }
if (model && model.rows && model.rows.length > 1) {
  var _lv = [], _ri;
  for (_ri = 0; _ri < model.rows.length; _ri++) _lv.push(model.rows[_ri].level);
  _lv.sort(sortNum);
  var _ms = medianStrikeStep(_lv);
  if (_ms && _ms > 0) PRICE_STEP = _ms;}
var sourceTag = model.sourceTag || 'SHADOW';
var sourceTxt = model.sourceText || 'ADVANCED SYNTHETIC';
var noteTxt = model.note || '';
var leftTotal = nz(model.leftTotal, 0), rightTotal = nz(model.rightTotal, 0);
var pcrVal = nz(model.pcr, 1.0);
var biasVal = nz(model.bias, 0);
var pocLevel = model.pocStrike || null;
var vahLevel = model.vahStrike || null;
var valLevel = model.valStrike || null;
var curLevel = currentPrice;
var atrLast = Math.max(nz(atr14[lastIdx], 1), EPS);
var pocPx = pocLevel, vahPx = vahLevel, valPx = valLevel, painPx = null;
if (model && model.maxPain != null) painPx = model.maxPain;
var QLAB = analyzeSurface(model, atrLast, currentPrice);
var cycleHalo = QLAB.cycleConf > 0.18 ? (0.5 + 0.5 * Math.sin(QLAB.cyclePhase)) : (0.5 + 0.5 * Math.sin(CLOCK.phase12 || 0));
// ============================================================================
// SIGNAL ENGINE
// ============================================================================
var longSignal = make(n, false), shortSignal = make(n, false);
var longLive = make(n, false), shortLive = make(n, false);
var longConf = make(n, false), shortConf = make(n, false);
var signalWhy = make(n, '');
var prevAbovePOC = null, prevInsideVA = null;
var lastFlowShift = -999;
for (i = Math.max(adaptivePeriod, 5); i < n; i++) {
  if (pocLevel === null || valLevel === null || vahLevel === null) { signalWhy[i] = 'no-levels'; continue; }
  var px = close[i];
  var distOk = (i - Math.max(lastPeakSignalBar, lastValleySignalBar)) >= minSignalDistance;
  var insideVA = px >= valLevel && px <= vahLevel;
  var abovePOC = px > pocLevel;
  var isSig = false, sigDir = 0, why = '';
  if (prevAbovePOC !== null && prevAbovePOC !== abovePOC && distOk && true) {
    isSig = true; sigDir = abovePOC ? 1 : -1; why = abovePOC ? 'POC↑' : 'POC↓'; }
  else if (prevInsideVA !== null && prevInsideVA !== insideVA) {
    if (!insideVA && distOk) { isSig = true; sigDir = px > vahLevel ? 1 : -1; why = px > vahLevel ? 'VA+BRK' : 'VA-BRK'; }
    else if (insideVA && distOk) { isSig = true; sigDir = px > pocLevel ? 1 : -1; why = 'VA-REJ'; }}
  if (!isSig && i === lastIdx && i > lastFlowShift + minSignalDistance) {
    var pcrNow = leftTotal > 0 ? leftTotal / rightTotal : 1;
    if (pcrNow > pcrHiThresh) { isSig = true; sigDir = -1; why = 'FLOW-S'; lastFlowShift = i; }
    else if (pcrNow < pcrLoThresh) { isSig = true; sigDir = 1; why = 'FLOW-L'; lastFlowShift = i; }}
  if (isSig) {
    if (sigDir === 1) { longSignal[i] = true; lastValleySignalBar = i; lastSignalType = 'Valley'; }
    else { shortSignal[i] = true; lastPeakSignalBar = i; lastSignalType = 'Peak'; }
    signalWhy[i] = why;}
  prevAbovePOC = abovePOC; prevInsideVA = insideVA;}
for (i = 0; i < n; i++) {
  if (!longSignal[i] && !shortSignal[i]) continue;
  if (i === lastIdx && i_intrabar === 'On') { longLive[i] = longSignal[i]; shortLive[i] = shortSignal[i]; }
  else { longConf[i] = longSignal[i]; shortConf[i] = shortSignal[i]; }}
register_signal(longConf, 'Options Ladder Pro: VA/POC Long');
register_signal(shortConf, 'Options Ladder Pro: VA/POC Short');
var peakConf = make(n, false), valleyConf = make(n, false);
for (i = 0; i < n; i++) {
  if (peakSignals[i] != null && !peakTentative[i]) peakConf[i] = true;
  if (valleySignals[i] != null && !valleyTentative[i]) valleyConf[i] = true;}
register_signal(valleyConf, 'Options Ladder Pro: Valley Long');
register_signal(peakConf, 'Options Ladder Pro: Peak Short');
var rPeakSig = paint(peakSignals, { name: 'OLP Peak Signals', color: 'transparent', affects_scale: false, show_in_legend: false });
var rValleySig = paint(valleySignals, { name: 'OLP Valley Signals', color: 'transparent', affects_scale: false, show_in_legend: false });
for (i = 0; i < n; i++) {
  if (peakSignals[i] != null) paint_label_at_line(rPeakSig, i, peakTentative[i] ? '▼!' : '▼', { color: peakTentative[i] ? T.warn : T.bear, background_color: 'transparent', border_color: 'transparent', vertical_align: 'top' });
  if (valleySignals[i] != null) paint_label_at_line(rValleySig, i, valleyTentative[i] ? '▲!' : '▲', { color: valleyTentative[i] ? T.warn : T.bull, background_color: 'transparent', border_color: 'transparent', vertical_align: 'bottom' });}
// ============================================================================
// MAIN CHART
// ============================================================================
var pocSeries = make(n, null), vahSeries = make(n, null), valSeries = make(n, null);
var lastPoc = null, lastVah = null, lastVal = null;
for (i = 0; i < n; i++) { pocSeries[i] = pocLevel; vahSeries[i] = vahLevel; valSeries[i] = valLevel; }
var pocBreach = make(n, null);
var prevPOCSide = null;
for (i = 1; i < n; i++) {
  if (pocLevel === null) continue;
  var side = close[i] > pocLevel ? 1 : -1;
  if (prevPOCSide !== null && side !== prevPOCSide) pocBreach[i] = side > 0 ? 1 : -1; prevPOCSide = side;}
var pocPulse = 0.5 + 0.5 * Math.sin(Date.now() / 800);
var gap = 4;
var pocSeries2 = make(n, null), vahSeries2 = make(n, null), valSeries2 = make(n, null);
for (var _gi = 0; _gi < n - gap; _gi++) { pocSeries2[_gi] = pocLevel; vahSeries2[_gi] = vahLevel; valSeries2[_gi] = valLevel; }
for (var _gi2 = n - gap; _gi2 < n; _gi2++) { pocSeries2[_gi2] = null; vahSeries2[_gi2] = null; valSeries2[_gi2] = null; }
pocSeries = pocSeries2; vahSeries = vahSeries2; valSeries = valSeries2;
paint(pocSeries, { name: 'POC', color: T.gold, thickness: 2, affects_scale: false });
paint(vahSeries, { name: 'VAH', color: rgba(T.bull, 0.55), thickness: 1, affects_scale: false });
paint(valSeries, { name: 'VAL', color: rgba(T.bear, 0.55), thickness: 1, affects_scale: false });
var pocLblY = make(n, null); pocLblY[lastIdx] = pocLevel || currentPrice;
var vahLblY = make(n, null); vahLblY[lastIdx] = vahLevel || currentPrice;
var valLblY = make(n, null); valLblY[lastIdx] = valLevel || currentPrice;
var rPocLbl = paint(pocLblY, { name: 'POC Label', color: 'transparent', affects_scale: false, show_in_legend: false });
var rVahLbl = paint(vahLblY, { name: 'VAH Label', color: 'transparent', affects_scale: false, show_in_legend: false });
var rValLbl = paint(valLblY, { name: 'VAL Label', color: 'transparent', affects_scale: false, show_in_legend: false });
if (pocLevel !== null) paint_label_at_line(rPocLbl, lastIdx, 'POC ' + fmtPrice(pocLevel), { color: T.gold, background_color: 'transparent', border_color: 'transparent', vertical_align: 'middle' });
if (vahLevel !== null) paint_label_at_line(rVahLbl, lastIdx, 'VAH ' + fmtPrice(vahLevel), { color: rgba(T.bull, 0.8), background_color: 'transparent', border_color: 'transparent', vertical_align: 'middle' });
if (valLevel !== null) paint_label_at_line(rValLbl, lastIdx, 'VAL ' + fmtPrice(valLevel), { color: rgba(T.bear, 0.8), background_color: 'transparent', border_color: 'transparent', vertical_align: 'middle' });
for (i = 1; i < n; i++) {
  if (pocBreach[i]) {paint_label_at_line(rPocLbl, i, pocBreach[i] > 0 ? '▲ POC' : '▼ POC', { color: pocBreach[i] > 0 ? T.bull : T.bear, background_color: 'transparent', border_color: 'transparent', vertical_align: 'middle' }); }}
// ============================================================================
// DASHBOARD HELPERS
// ============================================================================
var TH = { bg: 'var(--background-color)', tx: 'var(--text-color)', bdr: 'var(--border-color)', acc: THEME.acc, bul: THEME.bull, ber: THEME.bear, wrn: THEME.warn, pur: THEME.pur, dim: THEME.dim };
function hexA2(hex, alpha) {hex = hex.replace('#', '');
  if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
  var r = parseInt(hex.substring(0, 2), 16), g = parseInt(hex.substring(2, 4), 16), b = parseInt(hex.substring(4, 6), 16);
  return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';}
function pCell(t, c, al, b, cs, bg, w) { var o = { text: String(t), color: c || TH.tx, textAlign: al || 'left', fontWeight: b ? 'bold' : 'normal', colspan: cs || 1, background: bg || TH.bg, fontSize: 10 }; if (w) o.width = w; return o; }
function pRow3(a, b, c) { return { cells: [a, b, c] }; }
function pSect(t, col) { return { cells: [pCell(t, col, 'left', true, 3, hexA2(col || TH.acc, 0.10))] }; }
function confBar(pct, size) { size = size || 10; var filled = clamp(Math.round(clamp(pct, 0, 100) / 100 * size), 0, size); var s = '', k; for (k = 0; k < size; k++) s += k < filled ? '█' : '░'; return s; }
function scoreColor(v) { if (v == null) return TH.tx; if (v >= 90) return TH.bul; if (v >= 75) return TH.wrn; return TH.tx; }
function sliceOiMax(slices) {
    var m = EPS, si;
    for (si = 0; si < slices.length; si++) if (slices[si].totalOI > m) m = slices[si].totalOI;
    var m2 = EPS, ki2, t, keys;
    for (si = 0; si < slices.length; si++) { keys = Object.keys(slices[si].surface || {}); for (ki2 = 0; ki2 < keys.length; ki2++) { t = nz(slices[si].surface[keys[ki2]].totalOI, 0); if (t > m2) m2 = t; } }
    return m2; }
function worldX(strike) { return clamp((strike - currentPrice) / atrLast, -5.2, 5.2) * 6.6; }
function worldY(oi, oiMax) {
    var t = clamp01(oi / Math.max(oiMax, EPS));
    return Math.pow(t, 0.46) * 40; }
function worldZ(idx, count) { return (idx - (count - 1) / 2) * (46 / Math.max(count - 1, 1)); }
// ==============================================================================================
// MASS SPLIT — solid radial wedges (mass = radius, put/call hue), split ring, PCR hub, vector labels. Stretch-proof: x-range = ±R × true pixel aspect.
// ==============================================================================================
// vector label font: smooth polylines (arcs sampled) with round caps and a dark halo — reads like a clean sans face on the canvas
function msArcPts(cx, cy, rx, ry, a0, a1, n) { var o = [], i; for (i = 0; i <= n; i++) { var a = (a0 + (a1 - a0) * i / n) * Math.PI / 180; o.push([cx + Math.cos(a) * rx, cy + Math.sin(a) * ry]); } return o; }
function msCirc(cx, cy, r) { return msArcPts(cx, cy, r, r, 0, 360, 16); }
var MS_GLYPHS = {
  '0': [msArcPts(0.5, 0.5, 0.42, 0.5, 0, 360, 18)], '1': [[[0.18, 0.74], [0.5, 1], [0.5, 0]], [[0.22, 0], [0.78, 0]]],
  '2': [msArcPts(0.5, 0.68, 0.36, 0.32, 165, -25, 10).concat([[0.02, 0], [0.98, 0]])],
  '3': [[[0.08, 1], [0.9, 1], [0.45, 0.58]].concat(msArcPts(0.52, 0.29, 0.36, 0.29, 90, -180, 10))],
  '4': [[[0.74, 0], [0.74, 1], [0.04, 0.3], [0.98, 0.3]]],
  '5': [[[0.9, 1], [0.18, 1], [0.12, 0.56], [0.5, 0.62]].concat(msArcPts(0.5, 0.31, 0.38, 0.31, 90, -175, 10))],
  '6': [[[0.85, 0.97], [0.6, 1], [0.35, 0.92], [0.2, 0.7], [0.15, 0.35]].concat(msArcPts(0.5, 0.31, 0.35, 0.31, 180, -180, 14))],
  '7': [[[0.04, 1], [0.96, 1], [0.38, 0]]],
  '8': [msArcPts(0.5, 0.73, 0.3, 0.27, -90, 270, 12), msArcPts(0.5, 0.28, 0.36, 0.28, 90, 450, 12)],
  '9': [msArcPts(0.5, 0.69, 0.35, 0.31, 0, 360, 14).concat([[0.85, 0.69], [0.8, 0.3], [0.65, 0.08], [0.4, 0], [0.15, 0.03]])],
  '.': [msCirc(0.5, 0.08, 0.08)], '-': [[[0.15, 0.5], [0.85, 0.5]]],
  '%': [[[0.12, 0], [0.88, 1]], msCirc(0.24, 0.76, 0.17), msCirc(0.76, 0.24, 0.17)],
  'A': [[[0, 0], [0.5, 1], [1, 0]], [[0.19, 0.36], [0.81, 0.36]]],
  'B': [[[0, 0], [0, 1], [0.58, 1]].concat(msArcPts(0.58, 0.76, 0.26, 0.24, 90, -90, 8)).concat([[0, 0.52], [0.64, 0.52]]).concat(msArcPts(0.64, 0.26, 0.3, 0.26, 90, -90, 8)).concat([[0, 0]])],
  'C': [msArcPts(0.56, 0.5, 0.5, 0.5, 45, 315, 14)], 'E': [[[0.95, 1], [0, 1], [0, 0], [0.95, 0]], [[0, 0.5], [0.72, 0.5]]],
  'H': [[[0, 0], [0, 1]], [[1, 0], [1, 1]], [[0, 0.5], [1, 0.5]]], 'L': [[[0, 1], [0, 0], [0.95, 0]]],
  'M': [[[0, 0], [0, 1], [0.5, 0.36], [1, 1], [1, 0]]], 'N': [[[0, 0], [0, 1], [1, 0], [1, 1]]],
  'O': [msArcPts(0.5, 0.5, 0.48, 0.5, 0, 360, 18)],
  'P': [[[0, 0], [0, 1], [0.6, 1]].concat(msArcPts(0.6, 0.74, 0.3, 0.26, 90, -90, 8)).concat([[0, 0.48]])],
  'R': [[[0, 0], [0, 1], [0.6, 1]].concat(msArcPts(0.6, 0.74, 0.3, 0.26, 90, -90, 8)).concat([[0, 0.48]]), [[0.4, 0.48], [1, 0]]],
  'S': [msArcPts(0.5, 0.75, 0.34, 0.25, 40, 270, 10).concat(msArcPts(0.5, 0.25, 0.34, 0.25, 90, -140, 10))],
  'T': [[[0, 1], [1, 1]], [[0.5, 1], [0.5, 0]]], 'U': [[[0, 1], [0, 0.36]].concat(msArcPts(0.5, 0.36, 0.5, 0.36, 180, 360, 10)).concat([[1, 1]])],
  'V': [[[0, 1], [0.5, 0], [1, 1]]], 'W': [[[0, 1], [0.24, 0], [0.5, 0.62], [0.76, 0], [1, 1]]], 'Y': [[[0, 1], [0.5, 0.5], [1, 1]], [[0.5, 0.5], [0.5, 0]]], ' ': [] };
function msStrWidth(str, w) { return String(str).length * w; }
function msText(sets, str, x, y, w, h, col, width, order, halo) {   // x = left edge, y = baseline (data units); glyph cell 0.72·w wide, 0.28·w spacing; halo = dark outline for legibility over colour
  var pts = [], cx = x, c, k, q, sst = String(str).toUpperCase();
  for (c = 0; c < sst.length; c++) { var g = MS_GLYPHS[sst.charAt(c)]; if (g) for (k = 0; k < g.length; k++) { for (q = 0; q < g[k].length; q++) pts.push({ x: cx + g[k][q][0] * w * 0.72, y: y + g[k][q][1] * h }); pts.push({ x: null, y: null }); } cx += w; }
  if (!pts.length) return;
  if (halo !== false) sets.push({ label: 'mth' + sets.length, data: pts, showLine: true, fill: false, spanGaps: false, borderColor: rgba('#05060F', 0.78), borderWidth: (width || 1.3) + 2.4, borderCapStyle: 'round', borderJoinStyle: 'round', pointRadius: 0, tension: 0, order: (order || 3) + 1 });
  sets.push({ label: 'mtx' + sets.length, data: pts, showLine: true, fill: false, spanGaps: false, borderColor: col, borderWidth: width || 1.3, borderCapStyle: 'round', borderJoinStyle: 'round', pointRadius: 0, tension: 0, order: order || 3 }); }
function msArc(cx, cy, r, a0, a1, n) { var out = [], i; for (i = 0; i <= n; i++) { var a = a0 + (a1 - a0) * i / n; out.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }); } return out; }
function msHexMix(a, b, pct) { try { return tiny.mix(a, b, pct).toHexString(); } catch (e) { return a; } }
var MS_MIDC = '#FF9A3C';   // balanced put/call = amber-orange (distinct from the gold POC)
function buildPolarPack(mdl, aspect, wpx, hpx) {
  var sets = [], w, k, CX = 0, CY = 0, RMAX = 6.0, RIN = 1.6, WEDGES = 5, Rpad = 8.4;
  aspect = (aspect && aspect > 0.4) ? aspect : 1.3; hpx = hpx || 148;
  var PXU = hpx / (2 * Rpad);   // pixels per data unit (vertical) — every stroke width below is derived from it, so the fill stays solid at any size
  var rows = (mdl && mdl.rows) ? mdl.rows : [];
  var poc = mdl ? mdl.pocStrike : null, vah = mdl ? mdl.vahStrike : null, val = mdl ? mdl.valStrike : null;
  var buckets = [
    { key: 'ABV', short: 'ABV', name: 'ABOVE VAH', tot: 0, left: 0, right: 0, fresh: 0 },
    { key: 'UVA', short: 'UVA', name: 'POC → VAH', tot: 0, left: 0, right: 0, fresh: 0 },
    { key: 'POC', short: 'POC', name: 'POC MAGNET', tot: 0, left: 0, right: 0, fresh: 0 },
    { key: 'LVA', short: 'LVA', name: 'VAL → POC', tot: 0, left: 0, right: 0, fresh: 0 },
    { key: 'BLW', short: 'BLW', name: 'BELOW VAL', tot: 0, left: 0, right: 0, fresh: 0 } ];
  function classify(level, isPOC) {
    if (isPOC) return 2;
    if (val != null && isFinite(val) && level < val) return 4;
    if (vah != null && isFinite(vah) && level > vah) return 0;
    if (poc != null && isFinite(poc) && level < poc) return 3;
    if (poc != null && isFinite(poc) && level > poc) return 1;
    if (level < currentPrice) return 3;
    if (level > currentPrice) return 1;
    return 2; }
  var allL = 0, allR = 0;
  for (k = 0; k < rows.length; k++) {
    var rw = rows[k], bi = classify(rw.level, rw.isPOC), t = nz(rw.totalVal, nz(rw.leftVal, 0) + nz(rw.rightVal, 0));
    buckets[bi].tot += t; buckets[bi].left += nz(rw.leftVal, 0); buckets[bi].right += nz(rw.rightVal, 0); buckets[bi].fresh += nz(rw.freshOI, 0); allL += nz(rw.leftVal, 0); allR += nz(rw.rightVal, 0); }
  var all = 0; for (k = 0; k < WEDGES; k++) all += buckets[k].tot;
  if (all <= EPS) { for (k = 0; k < WEDGES; k++) buckets[k].tot = 1; all = WEDGES; }
  var maxT = buckets[0].tot; for (k = 1; k < WEDGES; k++) if (buckets[k].tot > maxT) maxT = buckets[k].tot;
  var items = [], maxPct = 0;
  for (k = 0; k < WEDGES; k++) {
    var pct = 100 * buckets[k].tot / all; if (pct > maxPct) maxPct = pct;
    var sideMix = buckets[k].tot > EPS ? buckets[k].right / buckets[k].tot : 0.5, col;
    try { col = (k === 2) ? T.gold : (sideMix < 0.5 ? tiny.mix(T.bear, MS_MIDC, Math.round(clamp01(sideMix / 0.5) * 100)) : tiny.mix(MS_MIDC, T.bull, Math.round(clamp01((sideMix - 0.5) / 0.5) * 100))).saturate(22).toHexString(); } catch (eCol) { col = k === 2 ? T.gold : (sideMix >= 0.5 ? T.bull : T.bear); }   // vivid put → amber → call ramp (no muddy red/green midpoint)
    items.push({ key: buckets[k].key, short: buckets[k].short, name: buckets[k].name, tot: buckets[k].tot, pct: pct, color: col, mix: sideMix, fresh: buckets[k].fresh > 0, left: buckets[k].left, right: buckets[k].right }); }
  var STEP_PX = 2.1, GAP = 0.055, i, sIdx;
  // guide rings 25 / 50 / 75 / 100 % of the largest bucket + faint back-plate
  sets.push({ label: 'mplate', data: msArc(CX, CY, (RIN + RMAX) / 2, 0, Math.PI * 2, 72), showLine: true, fill: false, borderColor: rgba(T.a1, 0.05), borderWidth: (RMAX - RIN) * PXU, pointRadius: 0, tension: 0, order: 40 });
  for (i = 1; i <= 4; i++) sets.push({ label: 'pring' + i, data: msArc(CX, CY, RIN + (RMAX - RIN) * i / 4, 0, Math.PI * 2, 72), showLine: true, fill: false, borderColor: rgba(T.dim, i === 4 ? 0.34 : 0.18), borderWidth: 1, borderDash: i === 4 ? [] : [3, 3], pointRadius: 0, tension: 0, order: 35 });
  for (w = 0; w < WEDGES; w++) {   // wedges: concentric arc strokes → solid sector with a radial gradient (dark core → bright rim), rim highlight, glow, fresh dots
    var it = items[w], a0 = -Math.PI / 2 + w * (Math.PI * 2 / WEDGES) + GAP, a1 = a0 + (Math.PI * 2 / WEDGES) - 2 * GAP, mid = (a0 + a1) / 2;
    var rOut = RIN + Math.pow(it.tot / maxT, 0.85) * (RMAX - RIN), span = rOut - RIN, nStep = Math.max(3, Math.ceil(span * PXU / STEP_PX)), stepU = span / nStep, arcN = Math.max(10, Math.round((a1 - a0) * rOut * PXU / 2.5));
    for (sIdx = 0; sIdx < nStep; sIdx++) {
      var rr = RIN + (sIdx + 0.5) * stepU, tq = sIdx / Math.max(nStep - 1, 1), hexq = msHexMix(msHexMix(it.color, '#000000', 42 - 42 * tq), '#FFFFFF', 18 * tq * tq);
      sets.push({ label: 'w' + w + '_' + sIdx, data: msArc(CX, CY, rr, a0, a1, arcN), showLine: true, fill: false, borderColor: rgba(hexq, 0.92), borderWidth: stepU * PXU * 1.35, pointRadius: 0, tension: 0, order: 30 }); }
    var glowA = w === 2 ? 0.22 + 0.22 * (typeof breathe === 'number' ? breathe : 0.5) : 0.16;
    sets.push({ label: 'wglow' + w, data: msArc(CX, CY, rOut + 0.12, a0, a1, arcN), showLine: true, fill: false, borderColor: rgba(it.color, glowA), borderWidth: 7, pointRadius: 0, tension: 0, order: 29 });
    sets.push({ label: 'wrim' + w, data: msArc(CX, CY, rOut, a0, a1, arcN), showLine: true, fill: false, borderColor: rgba(lighten(it.color, 18), 0.98), borderWidth: 2.2, pointRadius: 0, tension: 0, order: 20 });
    if (it.fresh) sets.push({ label: 'wfresh' + w, data: msArc(CX, CY, rOut + 0.42, a0 + 0.08, a1 - 0.08, Math.max(3, Math.round(arcN / 4))), showLine: false, pointStyle: 'circle', pointRadius: 1.6, pointBackgroundColor: rgba(T.txt, 0.9), pointBorderWidth: 0, order: 18 });
    // labels: bucket key just outside the rim, share inside the wedge (when it is tall enough)
    var lw = 0.58, lh = 0.82, keyR = RMAX + 0.95, kx = CX + Math.cos(mid) * keyR, ky = CY + Math.sin(mid) * keyR, keyTxt = it.short + ' ' + it.pct.toFixed(0) + '%';   // zone key and its share together on the outer ring
    msText(sets, keyTxt, kx - msStrWidth(keyTxt, lw) / 2 + lw * 0.14, ky - lh / 2, lw, lh, rgba(lighten(it.color, 10), 0.98), 1.25, 12); }
  var lShare = (allL + allR) > EPS ? allL / (allL + allR) : 0.5;
  var leftN = (mdl && mdl.leftName) ? mdl.leftName : 'PUTS', rightN = (mdl && mdl.rightName) ? mdl.rightName : 'CALLS';
  return { sets: sets, items: items, maxPct: maxPct, lShare: lShare,
    caption: 'radius = OI mass  ·  hue = ' + leftN + ' (red) → ' + rightN + ' (green)  ·  dots = fresh flow  ·  PCR ' + (isFinite(pcrVal) ? pcrVal.toFixed(2) : '—'),
    xmin: -Rpad * aspect, xmax: Rpad * aspect, ymin: -Rpad, ymax: Rpad };}
function polarChartCfg(pack, hpx) {
  return {
    width: '100%', height: hpx, type: 'line',
    data: { datasets: pack.sets },
    options: {
      devicePixelRatio: 2, maintainAspectRatio: false, animation: { duration: 0 },
      layout: { padding: { top: 0, bottom: 0, left: 0, right: 0 } },
      scales: {
        x: { type: 'linear', min: pack.xmin, max: pack.xmax, display: false, grid: { display: false } },
        y: { type: 'linear', min: pack.ymin, max: pack.ymax, display: false, grid: { display: false } } },
      plugins: { legend: { display: false }, tooltip: { enabled: false } },
      elements: { line: { tension: 0, fill: false } } }};}
function polarLegendRow(pack) {
  var cells = [], i, it, fs, extra, mid; mid = Math.floor(pack.items.length / 2);
  for (i = 0; i < pack.items.length; i++) {it = pack.items[i]; fs = Math.max(7, Math.round((subFontSize - 1) + it.pct / 18)); extra = (i === mid) ? (7 - pack.items.length) : 1;
    if (extra < 1) extra = 1;
    cells.push({ text: it.short + ' ' + it.pct.toFixed(0) + '%', color: it.color, textAlign: 'center', fontWeight: it.pct >= pack.maxPct - 0.01 ? 'bold' : 'normal', fontSize: fs, colspan: extra, background: 'linear-gradient(180deg,' + rgba(it.color, 0.28 + 0.40 * (it.pct / 100)) + ' 0%,' + rgba(it.color, 0.08) + ' 100%)', textShadow: i === mid ? '0 0 7px ' + rgba(it.color, 0.55) : undefined });}
  return { cells: cells }; }
// ==============================================================================================
// THE BOARD
// ==============================================================================================
function styleCell(cell, extra) { extra = extra || {};
  for (var sk in extra) {
    if (extra[sk] != null) cell[sk] = extra[sk]; }
  return cell;}
function pCell(t, c, al, b, cs, bg, w, fs) {
  var o = { text: String(t), color: c || TH.tx, textAlign: al || 'left', fontWeight: b ? 'bold' : 'normal', colspan: cs || 1, background: bg || TH.bg, fontSize: fs || baseFontSize || 10, padding: '1px 2px' };
  if (w) o.width = w;
  return o;}
function barBgLeft(pct, fillCol, baseBg) {
  var p = clamp01(pct) * 100;
  var inv = (100 - p).toFixed(2);
  return 'linear-gradient(90deg,' +
    hexA2(fillCol, 0) + ' 0%,' +
    hexA2(fillCol, 0) + ' ' + inv + '%,' +
    hexA2(fillCol, 0.50) + ' ' + inv + '%,' +
    hexA2(fillCol, 0.88) + ' 100%),' + (baseBg || 'transparent');}
function barBgRight(pct, fillCol, baseBg) {
  var p = (clamp01(pct) * 100).toFixed(2);
  return 'linear-gradient(90deg,' +
    hexA2(fillCol, 0.88) + ' 0%,' +
    hexA2(fillCol, 0.50) + ' ' + p + '%,' +
    hexA2(fillCol, 0) + ' ' + p + '%,' +
    hexA2(fillCol, 0) + ' 100%),' + (baseBg || 'transparent');}
function profileYAxis(fontSize) {
  return { display: true, position: 'right', type: 'category', grid: { display: false, drawTicks: false }, ticks: { color: T.dim, font: { size: fontSize || 8 }, autoSkip: true, maxTicksLimit: 10, padding: 0 } };}
function collectProfileSeries(srcRows) {
  var out = { labels: [], puts: [], calls: [], putCols: [], callCols: [], maxVal: EPS };
  var iP, pv, cv;
  for (iP = 0; iP < srcRows.length; iP++) {
    pv = nz(srcRows[iP].leftVal, 0);
    cv = nz(srcRows[iP].rightVal, 0);
    if (pv > out.maxVal) out.maxVal = pv;
    if (cv > out.maxVal) out.maxVal = cv;
    out.labels.push(fmtPrice(srcRows[iP].level));
    out.puts.push(pv);
    out.calls.push(cv);
    out.putCols.push(rgba(srcRows[iP].isPOC ? T.gold : T.bear, srcRows[iP].isPOC ? 0.90 : 0.70));
    out.callCols.push(rgba(srcRows[iP].isPOC ? T.gold : T.bull, srcRows[iP].isPOC ? 0.90 : 0.70)); }
  return out;}
function makeProfileChart(pack, wpx, hpx, yFont) {
  return {
    width: wpx + 'px', height: hpx + 'px', type: 'bar',
    data: {
      labels: pack.labels,
      datasets: [
        { label: model.leftName || 'PUTS',  data: pack.puts,  backgroundColor: pack.putCols,  borderWidth: 0, barPercentage: 0.88, categoryPercentage: 0.90 },
        { label: model.rightName || 'CALLS', data: pack.calls, backgroundColor: pack.callCols, borderWidth: 0, barPercentage: 0.88, categoryPercentage: 0.90 } ] },
    options: {
      devicePixelRatio: 2,
      maintainAspectRatio: false,
      animation: { duration: 0 },
      indexAxis: 'y',
      layout: { padding: { top: 0, bottom: 0, left: 0, right: 0 } },
      scales: {
        x: {display: false, grid: { display: false }, stacked: false, min: 0, max: pack.maxVal, beginAtZero: true },
        y: profileYAxis(yFont) },
      plugins: { legend: { display: false } } }};}
function polarCfgSized(pack, wpx, hpx) { var cfg = polarChartCfg(pack, hpx + 'px'); cfg.width = wpx + 'px'; cfg.height = hpx + 'px'; return cfg; }   // same fixed px box as the profile chart: equal columns, no ladder distortion
var CS = 12;
var LCS = 5;
var MCS = 2;
var RCS = 5;
var SIDE_W = '42%';
var MID_W  = '16%';
var boardRows = [];
var scanIdx = i_cometSpeed !== 'Off'
  ? Math.floor((CLOCK.beat * (i_cometSpeed === 'Fast' ? 3 : 0.7)) % Math.max(1, model.rowCount))
  : -1;
var breathe = CLOCK.hasClock ? (0.5 + 0.5 * Math.sin(CLOCK.wallMs / 600)) : 0.5;
var pocGlow = 0.14 + 0.14 * breathe;
var pcrCol = pcrVal > 1.15 ? T.bear : pcrVal < 0.85 ? T.bull : T.txt;
var biasCol = biasVal >= 0 ? T.bull : T.bear;
var resTxt = i_resMode.indexOf('Auto') >= 0 ? 'SYNCED' : i_resMode;
var putShare = (leftTotal + rightTotal) > EPS ? leftTotal / (leftTotal + rightTotal) : 0.5;
var splitPct = putShare * 100;
var splitLo = Math.max(0, splitPct - 10).toFixed(1);
var splitHi = Math.min(100, splitPct + 10).toFixed(1);
var fieldBarBg = 'linear-gradient(90deg,' +
  hexA2(T.bear, 0.55) + ' 0%,' +
  hexA2(T.bear, 0.32) + ' ' + splitLo + '%,' +
  hexA2(T.gold, 0.38) + ' ' + splitPct.toFixed(1) + '%,' +
  hexA2(T.bull, 0.32) + ' ' + splitHi + '%,' +
  hexA2(T.bull, 0.55) + ' 100%)';
var leftRef  = (model.refs && model.refs.leftRef)  || EPS;
var rightRef = (model.refs && model.refs.rightRef) || EPS;
boardRows.push({ cells: [styleCell(
  pCell('◇ OPTIONS LADDER PRO', T.a1, 'center', true, CS, 'linear-gradient(90deg,' + hexA2(T.bull, 0.18) + ' 0%,' + hexA2(T.a1, 0.26) + ' 50%,' + hexA2(T.bear, 0.18) + ' 100%)', '100%', headFontSize), { letterSpacing: '0.14em', textShadow: '0 0 10px ' + hexA2(T.a1, 0.55), padding: '3px 4px' } )] });
boardRows.push({ cells: [
  pCell('SRC  ' + sourceTxt, T.acc, 'left', true, LCS, hexA2(T.acc, 0.10), SIDE_W, baseFontSize),
  pCell('PCR  ' + pcrVal.toFixed(2), pcrCol, 'center', true, MCS, hexA2(pcrCol, 0.12), MID_W, baseFontSize),
  pCell('RES  ' + resTxt, T.acc, 'right', true, RCS, hexA2(T.acc, 0.10), SIDE_W, baseFontSize) ] });
boardRows.push({ cells: [
  styleCell(pCell('POC  ' + fmtPrice(pocLevel), T.gold, 'left', true, LCS, 'linear-gradient(90deg,' + hexA2(T.gold, 0.34 + pocGlow) + ' 0%, transparent 100%)', SIDE_W, headFontSize), { textShadow: '0 0 8px ' + hexA2(T.gold, 0.70) }),
  pCell('VAH  ' + fmtPrice(vahLevel), T.bull, 'center', true, MCS, hexA2(T.bull, 0.14), MID_W, subFontSize),
  pCell('VAL  ' + fmtPrice(valLevel), T.bear, 'right', true, RCS, 'linear-gradient(270deg,' + hexA2(T.bear, 0.34) + ' 0%, transparent 100%)', SIDE_W, headFontSize) ] });
boardRows.push({ cells: [
  pCell('BIAS  ' + fmtSignedPct(biasVal), biasCol, 'left', true, LCS, hexA2(biasCol, 0.10), SIDE_W, baseFontSize),
  pCell('EXP  ' + String(numExp), T.acc, 'center', true, MCS, hexA2(T.acc, 0.08), MID_W, baseFontSize),
  pCell(SESSION.label || '', SESSION.live ? T.bull : T.dim, 'right', true, RCS, SESSION.live ? hexA2(T.bull, 0.12) : 'transparent', SIDE_W, baseFontSize) ] });
boardRows.push({ cells: [styleCell(
  pCell('PUTS  ' + splitPct.toFixed(0) + '%     FIELD     ' + (100 - splitPct).toFixed(0) + '%  CALLS', T.txt, 'center', true, CS, fieldBarBg, '100%', subFontSize), { letterSpacing: '0.08em', textShadow: '0 0 6px ' + hexA2(T.gold, 0.35), padding: '2px 4px' } )] });
for (var ri = 0; ri < model.rows.length; ri++) {
  var row = model.rows[ri];
  var lv = row.level;
  var lftV = nz(row.leftVal, 0), rgtV = nz(row.rightVal, 0);
  var leftNorm  = lftV / Math.max(leftRef, EPS);
  var rightNorm = rgtV / Math.max(rightRef, EPS);
  var isPOC = !!row.isPOC, isVAH = !!row.isVAH, isVAL = !!row.isVAL, isCUR = !!row.isCUR, isATM = !!row.isATM;
  var fresh = i_freshGlow === 'On' && nz(row.freshOI, 0) > 0;
  var isScan = ri === scanIdx;
  var vertT = model.rows.length > 1 ? ri / (model.rows.length - 1) : 0.5;
  var atmo = mixTowards(T.bear, T.bull, vertT * 100);
  var putHot  = i_theme === 'Cyber Punk' ? '#FF0055' : i_theme === 'Gold Sovereign' ? '#FF3D00' : '#FF1744';
  var callHot = i_theme === 'Cyber Punk' ? '#39FF14' : i_theme === 'Gold Sovereign' ? '#FFF275' : '#00FFAA';
  var lftCol = isPOC ? T.gold : leftNorm  > 0.82 ? putHot  : leftNorm  > 0.35 ? mixTowards(T.bear, atmo, 18) : mixTowards(T.bear, T.dim, 28);
  var rgtCol = isPOC ? T.gold : rightNorm > 0.82 ? callHot : rightNorm > 0.35 ? mixTowards(T.bull, atmo, 18) : mixTowards(T.bull, T.dim, 28);
  if (fresh) { lftCol = lighten(lftCol, 10); rgtCol = lighten(rgtCol, 10); }
  var tag = isPOC ? ' POC' : isATM ? ' ATM' : isVAH ? ' VAH' : isVAL ? ' VAL' : isCUR ? ' ◈' : (fresh ? ' ●' : '');
  var midTxt = (isScan ? '›' : '') + fmtPrice(lv) + tag;
  var midCol = isPOC ? T.gold : isATM ? T.a1 : isVAH ? T.bull : isVAL ? T.bear : isScan ? T.warn : fresh ? T.warn : T.txt;
  var rowBg = 'transparent';
  if (isPOC) rowBg = hexA2(T.gold, 0.16 + pocGlow);
  else if (isVAH) rowBg = hexA2(T.bull, 0.10);
  else if (isVAL) rowBg = hexA2(T.bear, 0.10);
  else if (isATM || isCUR) rowBg = hexA2(T.a1, 0.10);
  if (fresh && !isPOC) rowBg = hexA2(T.warn, 0.10 + 0.06 * breathe);
  if (isScan) {rowBg = 'linear-gradient(90deg,' + hexA2(T.warn, 0.24) + ' 0%,' + hexA2(T.a1, 0.12) + ' 50%,' + hexA2(T.warn, 0.24) + ' 100%)'; }
  var leftCell = pCell(fmtOI(lftV), lftCol, 'right', isPOC || leftNorm > 0.82, LCS, barBgLeft(leftNorm, lftCol, rowBg), SIDE_W, baseFontSize);
  leftCell.fontFamily = 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace';
  leftCell.padding = '1px 3px 1px 0px';
  leftCell.whiteSpace = 'nowrap';
  if (fresh) leftCell.textShadow = '0 0 6px ' + hexA2(T.warn, 0.45);
  var midCell = pCell(midTxt, midCol, 'center', true, MCS, rowBg, MID_W, (isPOC || isATM) ? headFontSize : baseFontSize);
  midCell.padding = '1px 0px';
  midCell.whiteSpace = 'nowrap';
  if (isPOC) midCell.textShadow = '0 0 8px ' + hexA2(T.gold, 0.70);
  else if (isScan) midCell.textShadow = '0 0 7px ' + hexA2(T.warn, 0.55);
  var rightCell = pCell(fmtOI(rgtV), rgtCol, 'left', isPOC || rightNorm > 0.82, RCS, barBgRight(rightNorm, rgtCol, rowBg), SIDE_W, baseFontSize);
  rightCell.fontFamily = 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace';
  rightCell.padding = '1px 0px 1px 3px';
  rightCell.whiteSpace = 'nowrap';
  if (fresh) rightCell.textShadow = '0 0 6px ' + hexA2(T.warn, 0.45);
  boardRows.push({ cells: [leftCell, midCell, rightCell] });}
boardRows.push({ cells: [
  pCell((model.leftName || 'PUTS') + '  ' + fmtOI(leftTotal), T.bear, 'left', true, LCS, hexA2(T.bear, 0.10), SIDE_W, subFontSize),
  pCell('Σ  ' + fmtOI(nz(model.allTotal, leftTotal + rightTotal)), T.gold, 'center', true, MCS, hexA2(T.gold, 0.10), MID_W, subFontSize),
  pCell(fmtOI(rightTotal) + '  ' + (model.rightName || 'CALLS'), T.bull, 'right', true, RCS, hexA2(T.bull, 0.10), SIDE_W, subFontSize) ] });
var cogTxt = isFinite(QLAB.cog) ? fmtPrice(QLAB.cog) : '—';
var cogAtrTxt = isFinite(QLAB.cogATR) ? QLAB.cogATR.toFixed(2) : '—';
boardRows.push({ cells: [
  pCell('COG  ' + cogTxt, T.txt, 'left', false, LCS, 'transparent', SIDE_W, subFontSize),
  pCell('H  ' + (isFinite(QLAB.entropy) ? QLAB.entropy.toFixed(2) : '—'), T.acc, 'center', false, MCS, 'transparent', MID_W, subFontSize),
  pCell('ΔATR  ' + cogAtrTxt, T.dim, 'right', false, RCS, 'transparent', SIDE_W, subFontSize) ] });
boardRows.push({ cells: [
  pCell('STRUCT  ' + (isFinite(QLAB.structure) ? (QLAB.structure * 100).toFixed(0) + '%' : '—') + '  ' + (QLAB.specBars || ''), T.acc, 'left', false, 6, hexA2(T.acc, 0.06), '50%', subFontSize),
  pCell((signalWhy[lastIdx] ? 'SIG  ' + signalWhy[lastIdx] : ('SIG  ' + (lastSignalType || 'None'))) + '  ·  ' + (SESSION.label || ''), SESSION.live ? T.bull : T.dim, 'right', true, 6, SESSION.live ? hexA2(T.bull, 0.10) : 'transparent', '50%', subFontSize) ] });
if (noteTxt) {boardRows.push({ cells: [styleCell(
    pCell(noteTxt, T.dim, 'center', false, CS, 'transparent', '100%', subFontSize),
    { letterSpacing: '0.04em' } )] });}
var isBoth = i_embedMode === 'Both';
var halfW = Math.max(110, Math.floor(boardWidthPx * 0.46));
var chartHBoth = clamp(Math.round(halfW * 0.78), 110, 148);
if (i_embedMode === 'Profile') {
  var profA = collectProfileSeries(model.rows);
  var profHA = clamp(model.rows.length * 11, 140, 240);
  var profWA = Math.max(160, boardWidthPx - 16);
  boardRows.push({ cells: [pCell('◈  OI PROFILE', T.a1, 'center', true, CS, hexA2(T.a1, 0.10), '100%', subFontSize)] });
  boardRows.push({ cells: [{
    chart: makeProfileChart(profA, profWA, profHA, 8),
    colspan: CS, background_color: T.bg, border_color: 'transparent', border_width: 0, padding: '0px', width: '100%' }] });}
if (i_embedMode === 'Split') {
  var splitW = Math.max(160, boardWidthPx - 16);
  var splitH = 160;
  var pPackS = buildPolarPack(model, splitW / splitH, splitW, splitH);
  boardRows.push({ cells: [pCell('◈  MASS SPLIT', T.a1, 'center', true, CS, hexA2(T.a1, 0.10), '100%', subFontSize)] });
  boardRows.push({ cells: [{
    chart: polarCfgSized(pPackS, splitW, splitH),
    colspan: CS, background_color: T.bg, border_color: 'transparent', border_width: 0, padding: '0px', width: '100%' }] });
  boardRows.push({ cells: [pCell(pPackS.caption, T.dim, 'center', false, CS, 'transparent', '100%', Math.max(7, subFontSize - 1))] });}
if (isBoth) {
  var pRowsB = model.rows;
  if (pRowsB.length > 16) {
    var thinStep = Math.ceil(pRowsB.length / 16);
    var thinned = [];
    for (var pti = 0; pti < pRowsB.length; pti += thinStep) thinned.push(pRowsB[pti]);
    pRowsB = thinned; }
  var profB = collectProfileSeries(pRowsB);
  var pPackB = buildPolarPack(model, halfW / chartHBoth, halfW, chartHBoth);   // identical box to the OI profile beside it
  boardRows.push({ cells: [
    pCell('◈  OI PROFILE', T.a1, 'center', true, 6, hexA2(T.a1, 0.10), '50%', subFontSize),
    pCell('◈  MASS SPLIT', T.a1, 'center', true, 6, hexA2(T.a1, 0.10), '50%', subFontSize) ] });
  boardRows.push({ cells: [ {
      chart: makeProfileChart(profB, halfW, chartHBoth, 7),
      colspan: 6, background_color: T.bg, border_color: 'transparent', border_width: 0, padding: '0px', width: '50%' }, {
      chart: polarCfgSized(pPackB, halfW, chartHBoth),
      colspan: 6, background_color: T.bg, border_color: 'transparent', border_width: 0, padding: '0px', width: '50%' } ] });
  boardRows.push({ cells: [
    pCell((model.leftName || 'PUTS') + ' / ' + (model.rightName || 'CALLS') + ' by strike', T.dim, 'center', false, 6, 'transparent', '50%', Math.max(7, subFontSize - 1)),
    pCell(pPackB.caption, T.dim, 'center', false, 6, 'transparent', '50%', Math.max(7, subFontSize - 1)) ] });}
paint_overlay('OLP Board', { position: 'top_right', offset_x: 6, offset_y: 6, order: 'above_all' }, {background: T.bg, border: '1px solid ' + rgba(T.a1, 0.32), borderRadius: 8, width: boardWidth, padding: '2px', rows: boardRows});
// ============================================================================
// GUIDES
// ============================================================================
var GUIDE_BG = 'rgba(10,11,31,0.95)';
var GUIDE_BORD = '#3d2c5e';
var GUIDE_HEAD = '#C084FF';
var GUIDE_LBL = '#00CCFF';
var GUIDE_TXT = '#E0E0FF';
function guideRow(label, body) {
  return { cells: [{ text: label, color: GUIDE_LBL, textAlign: 'left', fontSize: 10, fontFamily: 'monospace', fontWeight: 'bold', width: '28%' }, { text: body, color: GUIDE_TXT, textAlign: 'left', fontSize: 9.5, fontFamily: 'monospace', width: '72%' }]};}
function buildGuideRows() {
  var gR = [];
  gR.push({ cells: [{ text: '◇ OPTIONS LADDER PRO — INPUT GUIDE ◇', color: GUIDE_HEAD, textAlign: 'center', fontWeight: 'bold', colspan: 2, background: 'rgba(170,0,255,0.15)', fontSize: 11 }] });
  gR.push(guideRow('Data Source', 'Auto detects the instrument: futures/index/crypto get SLF (volume profile), equities get the live options chain with Advanced Synthetic fallback. Override to force a source.'));
  gR.push(guideRow('Resolution', 'Auto (Range-Synced) = the Resolution Cascade: strike spacing syncs to your visible chart range. Zoom in for tight strikes, zoom out for significant ones. Fixed modes lock the spacing.'));
  gR.push(guideRow('Expirations', 'Number of nearest expirations aggregated into the OI surface. More = fuller magnet structure.'));
  gR.push(guideRow('Signal Sensitivity', 'Signal spacing and flow-shift thresholds. Loose = more signals. Strict = fewer, higher conviction.'));
  gR.push(guideRow('Momentum Filter', 'Zero-Lag MFI and CCI gate the signals: MFI over-threshold confirms peaks, under-threshold confirms valleys. MFI+CCI requires both. Off disables.'));
  gR.push(guideRow('Intra-bar Signals', 'On: signals on the forming bar show as orange LIVE markers and confirm or vanish at close. Alerts always fire on confirmed close.'));
  gR.push(guideRow('Embed Charts', 'Profile = horizontal OI-by-strike bar chart inside the board. Split = the Mass Split dial. Both = profile and dial side by side. Off = table only.'));
  gR.push(guideRow('Highlight Scan', 'The traveling highlight box that scans down the ladder rows. Fast = energetic. Slow = cinematic. Off = static.'));
  gR.push(guideRow('Fresh-Flow Glow', 'Strikes with new positioning today glow brighter than static OI. Uses live OI-change and daily volume data.'));
  gR.push(guideRow('Theme', 'Four color skins driving every overlay, cloud, line, raster and dashboard. Cosmetic only.'));
  gR.push({ cells: [{ text: 'Turn this panel off once you are comfortable.', color: 'rgba(255,255,255,0.4)', textAlign: 'center', colspan: 2, fontSize: 9 }] });
  return gR;}
var guideRows = i_showInputGuide ? buildGuideRows() : [];
paint_overlay('OLP Input Guide', { position: 'top_left', offset_x: 0, offset_y: 0, order: 'above_all' }, { background: i_showInputGuide ? GUIDE_BG : 'transparent', border: i_showInputGuide ? ('1px solid ' + GUIDE_BORD) : '0px solid transparent', borderRadius: 5, width: i_showInputGuide ? '480px' : '0px', rows: guideRows });
function buildVisualGuideRows() {
  var vR = [];
  vR.push({ cells: [{ text: '◇ OPTIONS LADDER PRO — VISUAL GUIDE ◇', color: GUIDE_HEAD, textAlign: 'center', fontWeight: 'bold', colspan: 2, background: 'rgba(170,0,255,0.15)', fontSize: 11 }] });
  vR.push(guideRow('POC Line', 'Gold line at the max-open-interest strike — the options magnet. Breathes via alpha pulse. Flashes when price crosses. The single most important level on the chart.'));
  vR.push(guideRow('VAH / VAL Lines', 'Value Area High and Low — 70% of open interest is between them. Dim when price is inside value (acceptance), brighten when price escapes (rejection or breakout).'));
  vR.push(guideRow('Strike Rails', 'Dotted lines at the strikes bracketing price — the ladder structure felt on the main chart without clutter.'));
  vR.push(guideRow('POC Cross Markers', '▲ POC or ▼ POC printed at bars where price crossed the POC level — the momentum confirmation.'));
  vR.push(guideRow('The Board', 'The animated strike ladder. PUTS bar left, strike + tag center, CALLS bar right. POC row pulses gold. Fresh-flow rows (new OI today) glow. The traveling highlight scans down the rows. Embedded Chart.js shows the full OI profile and Call/Put split.'));
  vR.push(guideRow('Mass Split dial', 'Five wedges = where the open interest sits: ABV (above VAH), UVA (POC→VAH), POC (the magnet, gold), LVA (VAL→POC), BLW (below VAL). Wedge radius = share of total OI mass (rings at 25 / 50 / 75 / 100% of the largest); wedge hue = put (red) → call (green) mix of that zone; white dots outside a rim = fresh flow today; the gold POC wedge breathes. Each key carries its share of total mass; the put/call ratio is in the caption and the board header.'));
  vR.push(guideRow('Signals', 'POC cross, VA breakout, VA rejection, and flow-shift signals — all gated by the momentum filter, all intra-bar capable, all registered for alerts.'));
  vR.push({ cells: [{ text: 'Switch to Input Guide or Off from the Guide Panel dropdown.', color: 'rgba(255,255,255,0.4)', textAlign: 'center', colspan: 2, fontSize: 9 }] });
  return vR;}
var visualGuideRows = i_showVisualGuide ? buildVisualGuideRows() : [];
paint_overlay('OLP Visual Guide', { position: 'top_left', offset_x: 0, offset_y: 0, order: 'above_all' }, { background: i_showVisualGuide ? GUIDE_BG : 'transparent', border: i_showVisualGuide ? ('1px solid ' + GUIDE_BORD) : '0px solid transparent', borderRadius: 5, width: i_showVisualGuide ? '560px' : '0px', rows: visualGuideRows });