// ============================================================================ // Project : anomaly-atlas // File : web/lib/charts.js // Purpose : Server-rendered SVG figures built from experiment results.json // Author : Simon-Pierre Boucher // Contact : contact@spboucher.ai // Data src : hfmarketdata.io (sole data source) // Created : 2026-08-12 // Modified : 2026-08-12 // Platform : macOS / Apple Silicon (arm64) — Node.js (deployed on MacLustr) // License : All rights reserved (research code) // ============================================================================ "use strict"; /* Figures follow the dataviz method: form by job, validated palette * (slots: blue #2a78d6, orange #eb6834 — all-pairs PASS on #fffdf9; gray * #898781 is de-emphasis ink, not a series), thin marks with 2px surface * rings, hairline grid, text in ink tokens (never series color), native * tooltips, selective direct labels. Every figure is generated from * the latest committed results.json — never hand-typed numbers. */ const path = require("path"); const BLUE = "#2a78d6"; const ORANGE = "#eb6834"; const GRAY = "#898781"; const SURFACE = "#fffdf9"; const GRID = "#e5e1d6"; const INK = "#1a1c20"; const MUTED = "#5d6167"; function esc(s) { return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); } function latestResults(C, experiment) { const run = C.listResultRuns().find((r) => r.experiment === experiment); if (!run) return null; const data = C.readJson(path.posix.join(run.rel, "results.json")); return data ? { data, run: run.timestamp } : null; } function fig(svg, caption) { return `<figure class="chart-fig">${svg}<figcaption>${caption}</figcaption></figure>`; } const AXIS_TXT = `font-size="11" fill="${MUTED}"`; // ------------------------------------------------------------------- expB function expBScatterPanel(rows, key, title, W, H, xLabelBottom) { const ML = 46, MR = 14, MT = 26, MB = 34; const iw = W - ML - MR, ih = H - MT - MB; const ys = rows.map((r) => r[key]); const ymin = Math.min(...ys, 0), ymax = Math.max(...ys, 0); const pad = (ymax - ymin) * 0.12 || 0.01; const y0 = ymin - pad, y1 = ymax + pad; const xp = (v) => ML + v * iw; const yp = (v) => MT + ih - ((v - y0) / (y1 - y0)) * ih; let g = `<text x="${ML}" y="${MT - 10}" font-size="12" font-weight="600" fill="${INK}">${esc(title)}</text>`; for (const t of [y0 + pad, 0, y1 - pad]) { const v = Math.round(t * 1000) / 1000; g += `<line x1="${ML}" y1="${yp(v)}" x2="${ML + iw}" y2="${yp(v)}" stroke="${GRID}" stroke-width="1"/>`; // skip an extreme tick label that would collide with the zero label if (v === 0 || Math.abs(yp(v) - yp(0)) > 14) { g += `<text x="${ML - 6}" y="${yp(v) + 4}" text-anchor="end" ${AXIS_TXT}>${v}</text>`; } } g += `<line x1="${ML}" y1="${yp(0)}" x2="${ML + iw}" y2="${yp(0)}" stroke="#c9c4b6" stroke-width="1"/>`; for (const t of [0, 0.5, 1]) { g += `<text x="${xp(t)}" y="${MT + ih + 16}" text-anchor="middle" ${AXIS_TXT}>${t}</text>`; } if (xLabelBottom) g += `<text x="${ML + iw / 2}" y="${H - 4}" text-anchor="middle" ${AXIS_TXT}>staleness (share of RTH minutes without a fresh print)</text>`; const extreme = rows.reduce((a, b) => (Math.abs(b[key]) > Math.abs(a[key]) ? b : a), rows[0]); for (const r of rows) { g += `<circle cx="${xp(r.staleness).toFixed(1)}" cy="${yp(r[key]).toFixed(1)}" r="4.5" fill="${BLUE}" stroke="${SURFACE}" stroke-width="2"><title>${esc(r.ticker)} — staleness ${r.staleness}, ${esc(title)} ${r[key]}`; } for (const r of [extreme]) { g += `${esc(r.ticker)}`; } return g; } function expBFigure(C) { const res = latestResults(C, "expB_artifact_baselines"); if (!res) return ""; const rows = Object.entries(res.data.per_ticker || {}) .map(([ticker, m]) => ({ ticker, ...m })) .filter((r) => Number.isFinite(r.staleness)); if (rows.length < 5) return ""; const W = 760, H = 250; const half = 372; const svg = ` ${expBScatterPanel(rows.filter((r) => Number.isFinite(r.ac1)), "ac1", "1min return AC1", half, H, true)} ${expBScatterPanel(rows.filter((r) => Number.isFinite(r["spy_leads_+1"])), "spy_leads_+1", "SPY leads +1 min (LOCF join)", half, H, true)} `; return fig(svg, `expB — artifact null levels, one dot per ticker (Q1 2024, RTH 1min). ` + `Bounce pushes AC1 negative and LOCF joins make SPY spuriously lead, both in proportion to staleness. ` + `Run ${esc(res.run)}, regenerated from results.json.`); } // ------------------------------------------------------------------- expC function expCFigure(C) { const res = latestResults(C, "expC_reversion_scan"); if (!res) return ""; const cells = (res.data.cells || []).filter((c) => Number.isFinite(c.vr30) && Number.isFinite(c.ac1)); if (!cells.length) return ""; for (const c of cells) c.vr30_excess = c.vr30 - (1 + 2 * c.ac1 * (1 - 1 / 30)); const rowsDef = []; for (const tf of ["1day", "30min", "5min", "1min"]) { for (const per of ["2000-2007", "2008-2015", "2014-2015"]) { const cs = cells.filter((c) => c.timeframe === tf && c.period === per); if (cs.length) rowsDef.push({ label: `${tf} · ${per}`, cells: cs }); } } const W = 760, ML = 150, MR = 16, MT = 30, RH = 30, MB = 46; const H = MT + rowsDef.length * RH + MB; const xmin = -0.32, xmax = 0.16; const iw = W - ML - MR; const xp = (v) => ML + ((Math.max(xmin, Math.min(xmax, v)) - xmin) / (xmax - xmin)) * iw; let g = ""; for (const t of [-0.3, -0.2, -0.1, 0, 0.1]) { g += ` ${t}`; } g += `VR(30) excess over the MA(1)-consistent null · negative = multi-lag reversion beyond any lag-1 effect`; const labeled = new Set( cells.filter((c) => c.fdr_vr30 && c.vr30_excess < -0.05) .sort((a, b) => a.vr30_excess - b.vr30_excess).slice(0, 3).map((c) => c.ticker + c.period + c.timeframe)); rowsDef.forEach((row, i) => { const y = MT + i * RH + RH / 2; g += `${esc(row.label)}`; for (const c of row.cells) { const surv = c.fdr_vr30 && c.vr30_excess < -0.05; const tip = `${c.ticker} ${c.timeframe} ${c.period}: VR30 ${c.vr30}, excess ${c.vr30_excess.toFixed(3)}${surv ? " (FDR survivor)" : ""}`; g += surv ? `${esc(tip)}` : `${esc(tip)}`; if (labeled.has(c.ticker + c.period + c.timeframe)) { g += `${esc(c.ticker)}`; } } }); const legend = ` FDR survivor (excess < −0.05) other scan cells (Level 0)`; const svg = `${legend}${g}`; return fig(svg, `expC — multi-lag reversion triage on TRAIN. One dot per ticker-cell; ` + `the MA(1)-consistent null absorbs all lag-1 effects (bounce included); values beyond the axis range ` + `pile at its edge. Run ${esc(res.run)}, regenerated from results.json.`); } // ------------------------------------------------------------------- expD function expDFigure(C) { const res = latestResults(C, "expD_leadlag_scan"); if (!res) return ""; const out = []; for (const window of ["2014-2015", "2006-2007"]) { const rows = (res.data.pairs || []) .filter((p) => p.window === window && p.raw_xcorr && p.fresh_xcorr && Number.isFinite(p.raw_xcorr["1"]) && Number.isFinite(p.fresh_xcorr["1"])) .sort((a, b) => (a.bucket + "").localeCompare(b.bucket + "") || b.raw_xcorr["1"] - a.raw_xcorr["1"]); if (!rows.length) continue; const W = 760, ML = 170, MR = 16, MT = 34, RH = 19, MB = 44; const H = MT + rows.length * RH + MB; const vals = rows.flatMap((p) => [p.raw_xcorr["1"], p.fresh_xcorr["1"]]); const xmin = Math.min(...vals, 0) - 0.01, xmax = Math.max(...vals, 0) + 0.01; const iw = W - ML - MR; const xp = (v) => ML + ((v - xmin) / (xmax - xmin)) * iw; let g = ""; const ticks = [0, 0.05, 0.1].filter((t) => t >= xmin && t <= xmax); for (const t of ticks) { g += ` ${t}`; } g += `cross-correlation at +1 min (leader → follower)`; let lastBucket = ""; rows.forEach((p, i) => { const y = MT + i * RH + RH / 2; if (p.bucket !== lastBucket) { lastBucket = p.bucket; g += `${esc(p.bucket.toUpperCase())}`; } const raw = p.raw_xcorr["1"], fresh = p.fresh_xcorr["1"]; const surv = p.fdr_fresh_p1 || p["fdr_fresh_+1"]; g += `${esc(p.pair)} ${esc(p.pair)} raw LOCF join: ${raw} ${esc(p.pair)} both-fresh: ${fresh}${surv ? " (FDR survivor)" : ""}`; }); const legend = ` raw LOCF join (artifact included) both-fresh (synchronized)`; out.push(fig( `${legend}${g}`, `expD — lead at +1 min per pair, ${window}: the gap between the raw join and the both-fresh ` + `subsample is the non-synchronicity artifact (T3), measured. Run ${esc(res.run)}, regenerated from results.json.` )); } return out.join(""); } // ------------------------------------------------------------------- expE function expEFigure(C) { const res = latestResults(C, "expE_calendar_scan"); if (!res) return ""; const t = res.data.calendar_tests || {}; const obs = t.observed_bp || {}, band = t.perm_band95_bp || {}; const names = Object.keys(obs); if (!names.length) return ""; let out = ""; // Panel 1 — observed effect vs permuted-calendar 95% band (interval + dot) { const W = 760, ML = 130, MR = 20, MT = 30, RH = 30, MB = 44; const H = MT + names.length * RH + MB; const vals = names.flatMap((n) => [obs[n], ...(band[n] || [])]); const xmin = Math.min(...vals) - 2, xmax = Math.max(...vals) + 2; const iw = W - ML - MR; const xp = (v) => ML + ((v - xmin) / (xmax - xmin)) * iw; let g = ""; for (const tick of [-20, -10, 0, 10, 20].filter((v) => v > xmin && v < xmax)) { g += ` ${tick}`; } g += `mean daily SPY return in class minus overall mean (bp) · gray bar = permuted-calendar 95% band`; names.forEach((n, i) => { const y = MT + i * RH + RH / 2; const [lo, hi] = band[n] || [0, 0]; g += `${esc(n.replace(/_/g, " "))} ${esc(n)}: observed ${obs[n]} bp — marginal p ${t.p_marginal?.[n]}, family-wise p ${t.p_familywise?.[n]}`; }); out += fig( `${g}`, `expE — all 8 pre-declared calendar tests on SPY (train 2000–2016): every observed effect (dot) sits ` + `inside its permuted-calendar 95% band (bar). Nothing survives; the last-survivor turn-of-month included. ` + `Run ${esc(res.run)}, regenerated from results.json.` ); } // Panel 2 — H20 intraday profile: |return| and EDGE spread by half-hour const prof = (res.data.h20_intraday_profile || {}).profile || []; if (prof.length) { const W = 760, ML = 52, MR = 16, MT = 34, MB = 46, H = 260; const iw = W - ML - MR, ih = H - MT - MB; const ys = prof.flatMap((p) => [p.median_abs_1min_ret_bp, p.median_edge_spread_bp]).filter(Number.isFinite); const ymax = Math.max(...ys) * 1.12; const xp = (i) => ML + (i / (prof.length - 1)) * iw; const yp = (v) => MT + ih - (v / ymax) * ih; let g = ""; for (const tick of [0, 2, 4, 6].filter((v) => v <= ymax)) { g += ` ${tick}`; } prof.forEach((p, i) => { if (i % 2 === 0) g += `${esc(p.bucket)}`; }); const series = [ ["median_abs_1min_ret_bp", BLUE, "median |1min return|"], ["median_edge_spread_bp", ORANGE, "median EDGE spread"], ]; for (const [key, color, label] of series) { const pts = prof.map((p, i) => [xp(i), yp(p[key])]).filter((q) => Number.isFinite(q[1])); g += ``; prof.forEach((p, i) => { if (Number.isFinite(p[key])) g += `${esc(p.bucket)} — ${esc(label)}: ${p[key]} bp`; }); } g += ` median |1min return| (bp) median EDGE spread (bp) half-hour bucket (RTH) · liquid 12, 1min, 2014–2015`; out += fig( `${g}`, `expE / H20 — the intraday artifact profile (taxonomy input): volatility is U-shaped (6.6 bp at the open, ` + `2.4 midday, 2.9 at the close); the spread declines monotonically (2.8 → 1.2 bp). Any "first-30-minutes" return ` + `claim faces 2–3× the midday artifact level. Run ${esc(res.run)}.` ); } return out; } // ------------------------------------------------------------------- expF function expFFigure(C) { const res = latestResults(C, "expF_multiple_testing"); if (!res) return ""; const f = res.data.funnel || {}; const stages = [ ["searched universe", f.universe_rules, "all scanned cells/pairs/classes (×2 signs)"], ["naive |t| > 1.96", f.naive_t196, "uncorrected in-sample t-test"], ["BH-FDR 5%", f.fdr_survivors, "false-discovery-rate correction"], ["Hansen SPA step-1", (f.spa_step1_survivors || []).length, "data-snooping correction"], ].filter((s) => Number.isFinite(s[1])); if (stages.length < 3) return ""; const W = 760, ML = 235, MR = 90, MT = 26, RH = 46, MB = 40; const H = MT + stages.length * RH + MB; const iw = W - ML - MR; const max = stages[0][1]; let g = ""; stages.forEach(([label, n, sub], i) => { const y = MT + i * RH; const w = Math.max(2, (n / max) * iw); g += `${esc(label)} ${esc(sub)} ${esc(label)}: ${n} rules (${Math.round((n / max) * 100)}%) ${n} (${Math.round((n / max) * 100)}%)`; }); g += `Survivors are gross and artifact-laden — costs (expG) are the next layer.`; const svg = `${g}`; return fig(svg, `expF — the survival curve: what fraction of the searched rule universe survives each ` + `statistical-correction layer on TRAIN. Statistical correction fixes the search, not the mechanism. ` + `Run ${esc(res.run)}, regenerated from results.json.`); } // ------------------------------------------------------------------- expG function expGFigure(C) { const res = latestResults(C, "expG_cost_frontier"); if (!res) return ""; const rows = (res.data.items || []) .filter((i) => Number.isFinite(i.kappa_star) && i.kappa_star > 0) .sort((a, b) => b.kappa_star - a.kappa_star); if (rows.length < 5) return ""; const W = 760, ML = 235, MR = 20, MT = 40, RH = 19, MB = 46; const H = MT + rows.length * RH + MB; const iw = W - ML - MR; const xmin = Math.log10(0.001), xmax = Math.log10(2); const xp = (v) => ML + ((Math.log10(Math.max(v, 0.001)) - xmin) / (xmax - xmin)) * iw; let g = ""; for (const [t, label] of [[0.001, "0.001"], [0.01, "0.01"], [0.1, "0.1"], [1, "1"]]) { g += ` ${label}`; } for (const [t, label] of [[0.25, "patient execution"], [1.0, "full half-spread"]]) { g += ` ${label}`; } g += `breakeven cost multiplier κ* (× half-spread paid per trade, log scale) — right of a line = survives that cost level`; rows.forEach((r, i) => { const y = MT + i * RH + RH / 2; g += `${esc(r.rule)} ${esc(r.rule)} — κ* = ${r.kappa_star} · gross ${r.gross_mean_daily_bp} bp/day · turnover ${r.turnover_per_day}/day · half-spread ${r.half_spread_bp} bp`; }); const svg = `${g}`; return fig(svg, `expG — the cost frontier: every rule that beat the artifact nulls AND the search correction ` + `dies when it must pay a fraction of its own half-spread (median κ* = ${res.data.summary?.kappa_star_median}). ` + `Run ${esc(res.run)}, regenerated from results.json.`); } // ------------------------------------------------------------------- expH function expHFigure(C) { const res = latestResults(C, "expH_oos_stability"); if (!res) return ""; const s = res.data.summary || {}; const fam = s.daily_family_median_vr30_excess || {}; const rows = [ ["daily reversal family (median VR30 excess)", fam.train_2008_2015, fam.validation, "alpha"], ["ES→SPY cross-serial corr (fresh, −1 min)", -0.032, s["es_spy_val_fresh_-1"], "alpha"], ["SPX→SPY staleness lead (+1 min)", 0.132, s["spx_spy_val_fresh_-1"], "artifact"], ].filter((r) => Number.isFinite(r[1]) && Number.isFinite(r[2])); if (rows.length < 2) return ""; const W = 760, ML = 300, MR = 30, MT = 40, RH = 52, MB = 20; const H = MT + rows.length * RH + MB; const iw = W - ML - MR; let g = ` train (2008–2015) validation (2016–2021)`; rows.forEach(([label, tr, va, kind], i) => { const y = MT + i * RH + RH / 2; const lim = Math.max(Math.abs(tr), Math.abs(va)) * 1.25; const xp = (v) => ML + ((v + lim) / (2 * lim)) * iw; g += `${esc(label)} ${kind === "artifact" ? "known artifact — persists OOS" : "candidate 'alpha' — collapses OOS"} ${esc(label)} — train: ${tr} ${esc(label)} — validation: ${va} ${tr} ${va}`; }); const svg = `${g}`; return fig(svg, `expH — the validation split, opened once: every candidate "alpha" collapses toward zero ` + `out-of-sample while the known staleness artifact persists. Vertical tick = zero (each row has its own ` + `scale). Run ${esc(res.run)}, regenerated from results.json.`); } const BUILDERS = { expB_artifact_baselines: expBFigure, expC_reversion_scan: expCFigure, expD_leadlag_scan: expDFigure, expE_calendar_scan: expEFigure, expF_multiple_testing: expFFigure, expG_cost_frontier: expGFigure, expH_oos_stability: expHFigure, }; /** Figures for an experiment page ("" when none apply). */ function figuresFor(experiment, C) { const builder = BUILDERS[experiment]; try { return builder ? builder(C) : ""; } catch { return ""; } } /** The most recent experiment figure, for the home page. */ function homeFigure(C) { for (const exp of ["expH_oos_stability", "expG_cost_frontier", "expF_multiple_testing", "expE_calendar_scan", "expD_leadlag_scan", "expC_reversion_scan", "expB_artifact_baselines"]) { const html = figuresFor(exp, C); if (html) return { experiment: exp, html: html.split("")[0] + "" }; } return null; } module.exports = { figuresFor, homeFigure };