"use client"; import React, { useState, useMemo, useCallback, useEffect } from "react"; import { useSearchParams, usePathname, useRouter } from "next/navigation"; import { FaCaretUp, FaCaretDown, FaBriefcase, FaChartLine, FaShieldAlt, FaSlidersH, FaUndo, FaStar, FaPlug, FaPlay, FaSave } from "react-icons/fa"; import { Line, AreaChart, Area, BarChart, Bar, ComposedChart, PieChart, Pie, Cell, RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, ScatterChart, Scatter, ReferenceLine, Brush, } from "recharts"; import { DashboardThemeContext, darkTheme, useTheme, themeForResolved, CHART_COLORS, STRATEGY_COLORS, FONT, } from "@/lib/theme"; import { useThemePreference } from "@/context/ThemeContext"; import { generateMarketData, runOptimisation, runBenchmarks, computeVaR, simulateEquityCurve, computeHRPWeightsArr, calculateRiskContributions, simulatePerAssetEquity, } from "@/lib/simulationEngine"; import { optimizePortfolio, setIbmQuantumToken, clearIbmQuantumToken, getIbmQuantumStatus, createLabRun, } from "@/lib/api"; import { usePortfolioLabMarketData } from "@/hooks/usePortfolioLabMarketData"; import { usePortfolioLabConfig } from "@/hooks/usePortfolioLabConfig"; import { useLedgerSession } from "@/context/LedgerSessionContext"; import { DEFAULT_TICKERS } from "@/lib/defaultUniverse"; import { MAX_IBM_VQE_ASSETS } from "@/lib/quantumPortfolioJobs"; import TickerSearch from "@/components/dashboard/TickerSearch"; import DataSourceBadge from "@/components/dashboard/DataSourceBadge"; import SensitivityLabPanel from "@/components/SensitivityLabPanel"; const TICKER_UNIVERSE_PRESETS = [ { name: "Mag 7", tickers: ["AAPL", "MSFT", "GOOGL", "META", "NVDA", "AMZN", "TSLA"] }, { name: "Finance tilt", tickers: ["JPM", "BAC", "GS", "MS", "C", "V", "MA", "BRK.B"] }, ]; const fmtAxis2 = (v) => (v == null || Number.isNaN(Number(v)) ? "" : Number(v).toFixed(2)); /** Short label for horizontal bar category axis (avoids tick overlap). */ function strategyChartLabel(name, maxLen = 26) { if (!name || name.length <= maxLen) return name; return `${name.slice(0, maxLen - 1)}…`; } /** Abramowitz–Stegun approximation; used for normal overlay on return histogram. */ function erfApprox(x) { const sign = x >= 0 ? 1 : -1; const ax = Math.abs(x); const a1 = 0.254829592; const a2 = -0.284496736; const a3 = 1.421413741; const a4 = -1.453152027; const a5 = 1.061405429; const p = 0.3275911; const t = 1 / (1 + p * ax); const y = 1 - (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-ax * ax)); return sign * y; } function stdNormalCDF(z) { return 0.5 * (1 + erfApprox(z / Math.SQRT2)); } function empiricalPercentiles(arr) { if (!arr.length) return null; const s = [...arr].sort((a, b) => a - b); const q = (p) => { const ix = (s.length - 1) * p; const lo = Math.floor(ix); const hi = Math.ceil(ix); if (lo === hi) return s[lo]; return s[lo] + (s[hi] - s[lo]) * (ix - lo); }; return { p5: q(0.05), p25: q(0.25), p50: q(0.5), p75: q(0.75), p95: q(0.95) }; } /** Six heuristic spokes from portfolio stats — not Fama–French. Used for current vs equal-weight benchmark. */ function computeStyleProxySpokes(sharpe, portReturn, portVol, nActive, wMax, nAssets) { const n = Math.max(nAssets, 1); const na = Math.max(0, nActive ?? 0); return { market: 0.70 + sharpe * 0.1, size: 0.30 + (1 - wMax) * 0.8, value: 0.40 + Math.min(portReturn * 2, 0.4), momentum: 0.50 + Math.max(sharpe * 0.1, 0), quality: 0.60 + Math.min(na / n, 0.4), lowVol: 0.80 - Math.min(portVol * 2, 0.5), }; } const STYLE_PROXY_FACTORS = [ { key: "market", label: "Market", formula: "0.70 + Sharpe × 0.1" }, { key: "size", label: "Size", formula: "0.30 + (1 − w_max) × 0.8" }, { key: "value", label: "Value", formula: "0.40 + min(ann. return × 2, 0.4)" }, { key: "momentum", label: "Momentum", formula: "0.50 + max(Sharpe × 0.1, 0)" }, { key: "quality", label: "Quality", formula: "0.60 + min(N_active / N, 0.4)" }, { key: "lowVol", label: "Low vol", formula: "0.80 − min(ann. vol × 2, 0.5)" }, ]; /** Narrative labels for stress cards (depth s is model input; loss uses σ_p from current weights). */ const STRESS_SCENARIOS = [ { name: "2008 GFC", shock: -0.5, mechanism: "Systemic risk-off; cross-asset ρ → 1, liquidity gap" }, { name: "COVID crash", shock: -0.34, mechanism: "Synchronized repricing; vol spike dominates short window" }, { name: "2022 rate shock", shock: -0.25, mechanism: "Duration / growth unwind; factor crowding" }, { name: "Flash crash", shock: -0.09, mechanism: "Microstructure stress; partial mean reversion intraday" }, ]; function stressPipelineAlgorithmNote(objective) { switch (objective) { case "hybrid": return "Hybrid pipeline: screen universe → QUBO selects a subset → continuous refinement (e.g. Markowitz) on Σ. Combinatorial cost is bounded before the convex solve; σ_p reflects that path."; case "qubo_sa": return "QUBO-SA solves a combinatorial subset problem; resulting weights feed σ_p. Stress remains an affine proxy on that volatility—not a QUBO energy landscape."; case "vqe": return "VQE yields a variational solution decoded to weights; σ_p is computed from w* on Σ like any other objective. Use as lab sanity check, not a device noise model."; default: return "Classical objectives (Markowitz, HRP, min-variance, equal-weight) optimize w* on the lab covariance. Cardinality and cap constraints change which risks enter σ_p."; } } function formatTooltipNumber(name, value) { if (typeof value !== "number") return value; const n = (name || "").toLowerCase(); if (n.includes("sharpe")) return value.toFixed(3); return value.toFixed(2); } const REGIMES = [ { key: "normal", label: "Normal", icon: "●", hint: "Baseline correlation & vol" }, { key: "bull", label: "Bull", icon: "▲", hint: "Higher drift, lower vol" }, { key: "bear", label: "Bear", icon: "▼", hint: "Risk-off tilt" }, { key: "volatile", label: "Volatile", icon: "◆", hint: "Wider spreads" }, ]; /** Hybrid: API auto when null. */ const K_SCREEN_PRESETS = [null, 8, 12, 15, 20]; const K_SELECT_PRESETS = [null, 3, 5, 8]; /** QUBO-SA: auto K when null. */ const QUBO_K_PRESETS = [null, 6, 8, 10, 12]; const TABS = [ { key: "portfolio", label: "Portfolio", icon: }, { key: "performance", label: "Performance", icon: }, { key: "risk", label: "Risk", icon: }, { key: "sensitivity", label: "Sensitivity", icon: }, ]; function ChartTooltip({ active, payload, label }) { const t = useTheme(); if (!active || !payload?.length) return null; return (
{label != null &&
{label}
} {payload.map((p, i) => (
{p.name || p.dataKey} {typeof p.value === "number" ? formatTooltipNumber(p.name || String(p.dataKey), p.value) : p.value}
))}
); } function MetricCard({ label, value, unit, delta, description, color, detail, formula, benchmarkNote, insight, tag, tagTone, progress, progressCaption, }) { const t = useTheme(); const [showTip, setShowTip] = useState(false); const hasExtra = detail || formula || benchmarkNote; const tipText = [formula, detail, benchmarkNote].filter(Boolean).join("\n\n"); const accent = color || t.accent; const tagPalette = { positive: { bg: t.greenDim, fg: t.green }, warning: { bg: t.accentWarmDim, fg: t.accentWarm }, negative: { bg: t.redDim, fg: t.red }, neutral: { bg: t.surfaceLight, fg: t.textMuted }, }; const tp = tagTone ? tagPalette[tagTone] : tagPalette.neutral; return (
hasExtra && setShowTip(true)} onMouseLeave={() => setShowTip(false)} >
{label}
{tag && ( {tag} )}
{value} {unit && {unit}}
{delta !== undefined && (
= 0 ? t.green : t.red, marginTop: 6, fontFamily: FONT.mono }}> {delta >= 0 ? : } {" "}{Math.abs(delta).toFixed(2)}% vs benchmark
)} {description &&
{description}
} {progress != null && Number.isFinite(progress) && (
{progressCaption && (
{progressCaption}
)}
)} {insight && (
{insight}
)} {hasExtra && showTip && (
{tipText}
)}
); } function SectionHeader({ children, subtitle, explainer }) { const t = useTheme(); return (
{children}
{subtitle &&

{subtitle}

} {explainer && (
What this view is for
{explainer}
)}
); } function ControlLabel({ children }) { const t = useTheme(); return (
{children}
); } /** Portfolio Lab — constraints card (parity with Strategy Builder constraints panel). */ function ConstraintsPanel({ children }) { const t = useTheme(); return (
Constraints
Same semantics as{" "} Strategy Builder{" "} (min/max weight, API-bound). Turnover & universe size are lab controls for synthetic data; regime above applies when not using live history.
{children}
); } function formatSliderDisplay(value, unit, step) { if (unit === "%") return `${(Number(value) * 100).toFixed(1)}%`; if (unit === " assets") return `${value} assets`; if (typeof value !== "number") return String(value); if (step >= 1) return String(Math.round(value)); return `${value.toFixed(3)}${unit || ""}`; } /** Card shell for major sidebar blocks (parity with Strategy / Quantum Engine). */ function SidebarSection({ title, subtitle, children, muted }) { const t = useTheme(); return (
{title}
{subtitle ? (
{subtitle}
) : null}
{children}
); } function KChipRow({ label, value, presets, onChange }) { const t = useTheme(); return (
{label}
{presets.map((k) => { const active = (k == null && value == null) || (k != null && value === k); return ( ); })}
); } /** Mean off-diagonal correlation (symmetric matrix). */ function avgPairwiseCorr(corr) { if (!corr?.length) return null; const n = corr.length; if (n < 2) return null; let s = 0; let c = 0; for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { if (i !== j) { s += corr[i][j]; c++; } } } return c ? s / c : null; } function UniverseLabFacts({ snap }) { const t = useTheme(); const [copied, setCopied] = useState(false); const { n, days, sectorCount, avgRho, meanAnnVol, meanAnnRet, symbolPreview, fullSymbolList, liveStandIn, marketMode, startDate, endDate, dataSeed, regimeLabel, } = snap; const ctxLine = liveStandIn ? `Showing synthetic stand-in until you load history (${startDate} → ${endDate}).` : marketMode === "synthetic" ? `Regime ${regimeLabel} · seed ${dataSeed} · ${days}d return paths drive covariance.` : `Window ${startDate} → ${endDate} · ${days}d series · API covariance.`; const cell = (label, val) => (
{label}
{val}
); return (

{ctxLine}

{cell("Assets", String(n))} {cell("Sectors", String(sectorCount))} {cell("History", `${days}d`)} {cell("Avg ρ", avgRho == null ? "—" : avgRho.toFixed(2))} {cell("μ̄ (ann.)", `${(meanAnnRet * 100).toFixed(1)}%`)} {cell("σ̄ (ann.)", `${(meanAnnVol * 100).toFixed(1)}%`)}
{symbolPreview || "—"}
{fullSymbolList ? ( ) : null}
); } function UniverseMainSection({ data, universeBrowse, setUniverseBrowse, setSelectedTickers }) { const t = useTheme(); const browseRows = useMemo(() => { if (universeBrowse === "current") { return (data?.assets ?? []).map((a) => ({ sym: a.name, sector: a.sector, annR: a.annReturn, annV: a.annVol, })); } const list = universeBrowse === "mag7" ? TICKER_UNIVERSE_PRESETS[0].tickers : universeBrowse === "finance" ? TICKER_UNIVERSE_PRESETS[1].tickers : universeBrowse === "default10" ? [...DEFAULT_TICKERS] : []; return list.map((sym) => ({ sym, sector: "—", annR: null, annV: null })); }, [universeBrowse, data]); const applyBrowsePreset = useCallback(() => { if (universeBrowse === "current") return; const list = universeBrowse === "mag7" ? TICKER_UNIVERSE_PRESETS[0].tickers : universeBrowse === "finance" ? TICKER_UNIVERSE_PRESETS[1].tickers : universeBrowse === "default10" ? [...DEFAULT_TICKERS] : []; if (list.length) setSelectedTickers([...list]); }, [universeBrowse, setSelectedTickers]); return (
Browse & apply universes

Pick a list to inspect. Presets match the sidebar chips; "Use in sidebar" copies symbols into Data universe for the next run.

{universeBrowse !== "current" && ( )}
{browseRows.length === 0 ? ( ) : ( browseRows.map((row, i) => ( )) )}
# Symbol Sector μ ann. σ ann.
No assets in view — load data or pick a preset.
{i + 1} {row.sym} {row.sector} {row.annR == null ? "—" : `${(row.annR * 100).toFixed(1)}%`} {row.annV == null ? "—" : `${(row.annV * 100).toFixed(1)}%`}
); } function Panel({ children, span, id }) { const t = useTheme(); return (
{children}
); } function EquityCurveTooltip({ active, payload, activeLabel, scale }) { const t = useTheme(); if (!active || !payload?.length) return null; const row = payload[0]?.payload; if (!row) return null; const s = scale || 1; const strat = row[activeLabel]; const ew = row._ew; const fmt = (v) => s > 1 ? `$${(v * s / 100).toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 0 })}` : fmtAxis2(v); return (
Day {row.day}
{activeLabel}: {fmt(strat)}
Equal Weight: {fmt(ew)}
Drawdown from peak: {fmtAxis2(row._dd)}%
{row._rv != null &&
Rolling ann. vol (~20d): {fmtAxis2(row._rv)}%
}
); } /** HTML legend below chart — avoids Recharts cramming all series into one unreadable row. */ function EquityCurveLegendBlock({ activeLabel, presetMeta, seriesHidden, onToggleSeries, onShowAllSeries }) { const t = useTheme(); const bench = [ { seriesKey: "Equal Weight", key: "ew", label: "Equal weight", color: STRATEGY_COLORS["Equal Weight"], dash: "5 4" }, { seriesKey: "HRP", key: "hrp", label: "HRP", color: STRATEGY_COLORS.HRP, dash: "5 4" }, { seriesKey: "Min Variance", key: "mv", label: "Min variance", color: "#64748b", dash: "5 4" }, ]; const isOn = (key) => !seriesHidden[key]; const chip = (seriesKey, swatch, label, mono) => { const visible = isOn(seriesKey); return ( ); }; return (
Series key
Axis titles sit on the chart (left and below the plot). Brush under the chart selects the window.{" "}
Active (filled area) — click to toggle
{chip( activeLabel, , activeLabel, true, )}
Benchmarks (same covariance)
{bench.map((b) => ( {chip( b.seriesKey, , b.label, true, )} ))}
{presetMeta.length > 0 && ( <>
Sidebar presets (re-simulated)
{presetMeta.map((m) => ( {chip( m.dataKey, , m.name, true, )} ))}
)}
); } function SliderControl({ label, value, onChange, min, max, step, unit, info, disabled }) { const t = useTheme(); const pct = ((value - min) / (max - min)) * 100; return (
{label} {formatSliderDisplay(value, unit, step)}
onChange(parseFloat(e.target.value))} disabled={disabled} style={{ width: "100%", height: 6, appearance: "none", background: `linear-gradient(to right, ${t.accent} ${pct}%, ${t.border} ${pct}%)`, borderRadius: 3, outline: "none", cursor: disabled ? "not-allowed" : "pointer" }} /> {info &&
{info}
}
); } export default function QuantumPortfolioDashboard() { const { objectiveOptions, presetOptions, loading: configLoading, loadError: configLoadError, usingFallback: configUsingFallback, } = usePortfolioLabConfig(); const [nAssets, setNAssets] = useState(20); const [regime, setRegime] = useState("normal"); const [objective, setObjective] = useState("hybrid"); const [cardinality, setCardinality] = useState(null); const [kScreen, setKScreen] = useState(null); const [kSelect, setKSelect] = useState(null); const [weightMin, setWeightMin] = useState(0.005); const [weightMax, setWeightMax] = useState(0.20); const [turnoverLimit, setTurnoverLimit] = useState(0.20); const [dataSeed, setDataSeed] = useState(42); const [notional, setNotional] = useState(100000); const [activeTab, setActiveTab] = useState("portfolio"); // IBM Quantum state const [ibmToken, setIbmToken] = useState(""); const [ibmStatus, setIbmStatus] = useState({ configured: false, backends: [] }); const [ibmLoading, setIbmLoading] = useState(false); const [selectedTickers, setSelectedTickers] = useState([]); /** main panel: current lab vs named presets for browse table */ const [universeBrowse, setUniverseBrowse] = useState("current"); const [holdingsSort, setHoldingsSort] = useState({ col: "weight", asc: false }); const [corrHover, setCorrHover] = useState(null); /** Cumulative chart: seriesKey -> true when hidden (click legend to toggle). */ const [equitySeriesHidden, setEquitySeriesHidden] = useState({}); // Backend optimization state const [apiResult, setApiResult] = useState(null); const [optimizeLoading, setOptimizeLoading] = useState(false); const [optimizeError, setOptimizeError] = useState(null); const [runSaving, setRunSaving] = useState(false); const { resolved: globalResolvedTheme } = useThemePreference(); const t = themeForResolved(globalResolvedTheme); const router = useRouter(); const pathname = usePathname(); const { session, setLastOptimize, setUniverse } = useLedgerSession(); const sessionHydrated = React.useRef(false); useEffect(() => { if (sessionHydrated.current) return; sessionHydrated.current = true; const { objective: sObj, constraints: c, tickers: sTickers } = session; if (sObj && sObj !== objective) setObjective(sObj); if (c.weightMin != null && !Number.isNaN(Number(c.weightMin))) { setWeightMin(Number(c.weightMin)); } if (c.weightMax && c.weightMax !== weightMax) setWeightMax(c.weightMax); if (c.kScreen) setKScreen(Number(c.kScreen)); if (c.kSelect) setKSelect(Number(c.kSelect)); const isDefaultUniverse = sTickers.length === DEFAULT_TICKERS.length && [...sTickers].sort().join(",") === [...DEFAULT_TICKERS].sort().join(","); const shouldSyncTickers = !isDefaultUniverse || session.lastOptimize != null; if (shouldSyncTickers && sTickers.length > 0) { setSelectedTickers([...sTickers]); setNAssets(Math.min(sTickers.length, 30)); } }, []); // eslint-disable-line react-hooks/exhaustive-deps const validObjectiveIds = useMemo( () => new Set(objectiveOptions.map((o) => o.value)), [objectiveOptions] ); useEffect(() => { if (configLoading) return; if (validObjectiveIds.has(objective)) return; const fallback = (validObjectiveIds.has("hybrid") && "hybrid") || objectiveOptions[0]?.value || "hybrid"; setObjective(fallback); }, [configLoading, objective, validObjectiveIds, objectiveOptions]); const resetAll = useCallback(() => { setObjective("hybrid"); setCardinality(null); setKScreen(null); setKSelect(null); setWeightMin(0.005); setWeightMax(0.20); setTurnoverLimit(0.20); setNAssets(20); setRegime("normal"); setDataSeed(42); setSelectedTickers([]); }, []); const applyPreset = useCallback((p) => { setNAssets(p.nAssets); setObjective(p.objective); setWeightMin(p.minWeight); setWeightMax(p.maxWeight); setRegime(p.regime); }, []); // Fetch IBM Quantum status on mount useEffect(() => { getIbmQuantumStatus().then(setIbmStatus).catch(() => {}); }, []); // Clear API result when optimization-relevant params change useEffect(() => { setApiResult(null); setOptimizeError(null); }, [nAssets, regime, objective, cardinality, kScreen, kSelect, weightMin, weightMax, dataSeed]); const handleIbmConnect = useCallback(async () => { if (!ibmToken.trim()) return; setIbmLoading(true); try { await setIbmQuantumToken(ibmToken.trim()); const status = await getIbmQuantumStatus(); setIbmStatus(status); setIbmToken(""); } catch (err) { setIbmStatus(prev => ({ ...prev, error: err.message })); } finally { setIbmLoading(false); } }, [ibmToken]); const handleIbmDisconnect = useCallback(async () => { setIbmLoading(true); try { await clearIbmQuantumToken(); setIbmStatus({ configured: false, backends: [] }); } catch { // ignore } finally { setIbmLoading(false); } }, []); const { marketMode, setMarketMode, tickerInput: _tickerInput, setTickerInput, startDate, setStartDate, endDate, setEndDate, liveLoading, liveError: liveMarketError, loadLiveMarketData, data, isLiveLoaded, } = usePortfolioLabMarketData(nAssets, setNAssets, regime, dataSeed); void _tickerInput; useEffect(() => { if (selectedTickers.length > 0 && nAssets > selectedTickers.length) { setNAssets(selectedTickers.length); } }, [selectedTickers, nAssets, setNAssets]); useEffect(() => { setTickerInput(selectedTickers.join(",")); }, [selectedTickers, setTickerInput]); const handleRunOptimize = useCallback(async () => { if (!data?.assets?.length) return; setOptimizeLoading(true); setOptimizeError(null); try { const n = data.assets.length; const returns = data.assets.map(a => a.annReturn); const covariance = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => data.assets[i].annVol * data.assets[j].annVol * data.corr[i][j] ) ); const payload = { returns, covariance, asset_names: data.assets.map(a => a.name), sectors: data.assets.map(a => a.sector), objective, weight_min: weightMin, maxWeight: weightMax, seed: dataSeed, }; if (cardinality != null) payload.K = cardinality; if (kScreen != null) payload.K_screen = kScreen; if (kSelect != null) payload.K_select = kSelect; const resp = await optimizePortfolio(payload); const qsw = resp.qsw_result || resp; const apiSnapshot = { weights: qsw.weights || resp.weights || [], sharpe: qsw.sharpe_ratio ?? resp.sharpe_ratio ?? 0, portReturn: qsw.expected_return ?? resp.expected_return ?? 0, portVol: qsw.volatility ?? resp.volatility ?? 0, nActive: qsw.n_active ?? resp.n_active ?? 0, stage_info: resp.stage_info || null, holdings: resp.holdings || null, sector_allocation: resp.sector_allocation || null, risk_metrics: resp.risk_metrics || null, benchmarks: resp.benchmarks || null, }; setApiResult(apiSnapshot); const usedTickers = data.assets.map(a => a.name); setLastOptimize( { at: new Date().toISOString(), tickers: usedTickers, objective, constraints: { weightMin, weightMax }, payload: qsw, }, { source: "portfolio_lab" } ); setUniverse(usedTickers, objective); } catch (err) { setOptimizeError(err.message || "Optimization failed"); } finally { setOptimizeLoading(false); } }, [data, objective, cardinality, kScreen, kSelect, weightMin, weightMax, dataSeed, setLastOptimize, setUniverse]); const buildLabRunPayload = useCallback(() => { if (!data?.assets?.length) return null; const n = data.assets.length; const returns = data.assets.map(a => a.annReturn); const covariance = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => data.assets[i].annVol * data.assets[j].annVol * data.corr[i][j] ) ); const payload = { returns, covariance, asset_names: data.assets.map(a => a.name), sectors: data.assets.map(a => a.sector), objective, weight_min: weightMin, weight_max: weightMax, seed: dataSeed, data_mode: isLiveLoaded ? "live" : "synthetic", regime, tickers: data.assets.map(a => a.name), }; if (cardinality != null) payload.K = cardinality; if (kScreen != null) payload.K_screen = kScreen; if (kSelect != null) payload.K_select = kSelect; return payload; }, [data, objective, cardinality, kScreen, kSelect, weightMin, weightMax, dataSeed, isLiveLoaded, regime]); const handleSaveRun = useCallback(async () => { const payload = buildLabRunPayload(); if (!payload) return; setRunSaving(true); try { const resp = await createLabRun(payload); router.push(`/reports/runs/${resp.run_id}`); } catch (err) { setOptimizeError(err.message || "Failed to save run"); } finally { setRunSaving(false); } }, [buildLabRunPayload, router]); const handleSaveIbmVqeRun = useCallback(async (ibm_backend_mode) => { const payload = buildLabRunPayload(); if (!payload) return; if (ibm_backend_mode === "hardware") { if (typeof window !== "undefined") { const ok = window.confirm( "Run VQE on IBM quantum hardware? This uses IBM Quantum queue time and account credits." ); if (!ok) return; } } setRunSaving(true); try { const p = { ...payload, objective: "vqe", ibm_backend_mode }; const resp = await createLabRun(p, { execution_kind: "ibm_runtime" }); router.push(`/reports/runs/${resp.run_id}`); } catch (err) { setOptimizeError(err.message || "Failed to start IBM VQE run"); } finally { setRunSaving(false); } }, [buildLabRunPayload, router]); const ibmVqeEligible = useMemo(() => { const n = data?.assets?.length ?? 0; return Boolean(ibmStatus.configured && n > 0 && n <= MAX_IBM_VQE_ASSETS); }, [data?.assets?.length, ibmStatus.configured]); const simResult = useMemo(() => { if (!data?.assets?.length) return { weights: [], portReturn: 0, portVol: 0, sharpe: 0, nActive: 0, stage_info: null }; return runOptimisation(data, { objective, K: cardinality, KScreen: kScreen, KSelect: kSelect, wMin: weightMin, wMax: weightMax }); }, [data, objective, cardinality, kScreen, kSelect, weightMin, weightMax]); const result = apiResult || simResult; const isApiMode = !!apiResult; const dataUniverseSnap = useMemo(() => { const assets = data?.assets ?? []; const n = assets.length; const days = assets[0]?.returns?.length ?? 0; const sectorCount = new Set(assets.map((a) => a.sector)).size; const avgRho = avgPairwiseCorr(data?.corr); const meanAnnVol = n > 0 ? assets.reduce((a, x) => a + x.annVol, 0) / n : 0; const meanAnnRet = n > 0 ? assets.reduce((a, x) => a + x.annReturn, 0) / n : 0; const names = assets.map((a) => a.name); const symbolPreview = names.length === 0 ? "" : names.length <= 6 ? names.join(", ") : `${names.slice(0, 6).join(", ")} +${names.length - 6}`; const liveStandIn = marketMode === "live" && !isLiveLoaded; const regimeLabel = REGIMES.find((r) => r.key === regime)?.label ?? regime; return { n, days, sectorCount, avgRho, meanAnnVol, meanAnnRet, symbolPreview, fullSymbolList: names.join(", "), liveStandIn, marketMode, startDate, endDate, dataSeed, regimeLabel, }; }, [data, marketMode, isLiveLoaded, startDate, endDate, dataSeed, regime]); const benchmarks = useMemo(() => { if (!data?.assets) return { equalWeight: { sharpe: 0, portReturn: 0, portVol: 0, weights: [] }, minVariance: { sharpe: 0, portReturn: 0, portVol: 0, weights: [] }, riskParity: { sharpe: 0, portReturn: 0, portVol: 0, weights: [] }, maxSharpe: { sharpe: 0, portReturn: 0, portVol: 0, weights: [] } }; return runBenchmarks(data); }, [data]); /** Heuristic radar: same formulas for optimized book vs equal-weight on identical Σ (apples-to-apples). */ const styleProxyRadar = useMemo(() => { const buildRadiusTicks = (domainMax, n = 5) => { if (!(domainMax > 0)) return [0]; const raw = []; for (let i = 0; i < n; i += 1) { const v = i === n - 1 ? domainMax : (i / (n - 1)) * domainMax; raw.push(Number(v.toFixed(6))); } return [...new Set(raw)].sort((a, b) => a - b); }; if (!data?.assets?.length || !result?.weights?.length) { const domainMax = 1.5; return { rows: [], domainMax, radiusTicks: buildRadiusTicks(domainMax) }; } const n = data.assets.length; const ew = benchmarks.equalWeight; const wEw = 1 / Math.max(n, 1); const p = computeStyleProxySpokes(result.sharpe, result.portReturn, result.portVol, result.nActive, weightMax, n); const b = computeStyleProxySpokes(ew.sharpe, ew.portReturn, ew.portVol, n, wEw, n); const rows = STYLE_PROXY_FACTORS.map((f) => ({ factor: f.label, portfolio: p[f.key], benchmark: b[f.key], formula: f.formula, key: f.key, })); let maxV = 0; rows.forEach((r) => { maxV = Math.max(maxV, r.portfolio, r.benchmark); }); const domainMax = Math.min(2.25, Math.max(1.2, Math.ceil(maxV * 11) / 10)); return { rows, domainMax, radiusTicks: buildRadiusTicks(domainMax) }; }, [data, result, benchmarks, weightMax]); const riskMetrics = useMemo(() => { if (isApiMode && apiResult.risk_metrics) { return { var95: (apiResult.risk_metrics.var_95 ?? 0) * 100, cvar: (apiResult.risk_metrics.cvar ?? 0) * 100 }; } if (!data || !result?.weights?.length) return { var95: 0, cvar: 0 }; return computeVaR(data, result.weights, 0.95); }, [data, result.weights, isApiMode, apiResult]); const holdings = useMemo(() => { if (isApiMode && apiResult.holdings) { return apiResult.holdings.map(h => ({ name: h.name, sector: h.sector, weight: h.weight, annReturn: 0, annVol: 0, sharpe: 0, })).sort((a, b) => b.weight - a.weight); } if (!data?.assets || !result?.weights) return []; return data.assets.map((a, i) => ({ name: a.name, sector: a.sector, weight: result.weights[i] || 0, annReturn: a.annReturn, annVol: a.annVol, sharpe: a.sharpe })) .filter(h => h.weight > 0.005).sort((a, b) => b.weight - a.weight); }, [data, result, isApiMode, apiResult]); const sectorData = useMemo(() => { if (isApiMode && apiResult.sector_allocation) { return apiResult.sector_allocation.map(s => ({ name: s.sector, value: Math.round(s.weight * 1000) / 10 })).sort((a, b) => b.value - a.value); } const sectors = {}; holdings.forEach(h => { sectors[h.sector] = (sectors[h.sector] || 0) + h.weight; }); return Object.entries(sectors).map(([name, value]) => ({ name, value: Math.round(value * 1000) / 10 })).sort((a, b) => b.value - a.value); }, [holdings, isApiMode, apiResult]); const riskReturnScatter = useMemo(() => { if (!data?.assets || !result?.weights) return []; return data.assets.map((a, i) => ({ name: a.name, x: a.annVol * 100, y: a.annReturn * 100, z: (result.weights[i] || 0) * 100, sector: a.sector, inPortfolio: (result.weights[i] || 0) > 0.005 })); }, [data, result]); const fundedPortfolio = useMemo(() => { if (!data?.assets?.length || !result?.weights?.length) { return { total: [], finalPositions: [], summary: { notional, currentValue: notional, totalPnl: 0, totalReturnPct: 0 } }; } return simulatePerAssetEquity(data, result.weights, 504, notional); }, [data, result, notional]); const concentrationMetrics = useMemo(() => { const w = result?.weights ?? []; const active = w.filter((x) => x > 0.005); const hhi = w.reduce((a, x) => a + x * x, 0); const effectiveN = hhi > 0 ? 1 / hhi : 0; const sorted = [...active].sort((a, b) => b - a); const top5 = sorted.slice(0, 5).reduce((a, b) => a + b, 0); const maxW = sorted[0] ?? 0; const minW = sorted.length ? sorted[sorted.length - 1] : 0; return { hhi, effectiveN, top5, maxW, minW, nActive: active.length }; }, [result]); const activeLabel = objectiveOptions.find(o => o.value === objective)?.label || objective; const searchParams = useSearchParams(); useEffect(() => { const tab = searchParams.get("tab"); if (tab && TABS.some((x) => x.key === tab)) { setActiveTab(tab); } }, [searchParams]); const setTab = useCallback( (key) => { setActiveTab(key); const params = new URLSearchParams(searchParams.toString()); params.set("tab", key); router.replace(`${pathname}?${params.toString()}`, { scroll: false }); }, [pathname, router, searchParams], ); useEffect(() => { setEquitySeriesHidden({}); }, [objective]); const toggleEquitySeries = useCallback((seriesKey) => { setEquitySeriesHidden((prev) => ({ ...prev, [seriesKey]: !prev[seriesKey] })); }, []); const showAllEquitySeries = useCallback(() => { setEquitySeriesHidden({}); }, []); /** Cumulative $100 equity: active strategy + benchmarks on current Σ, plus one curve per sidebar preset (own N/regime/constraints, same seed). */ const { equityCurves, equityPresetLineMeta } = useMemo(() => { if (!result.weights?.length) return { equityCurves: [], equityPresetLineMeta: [] }; const main = simulateEquityCurve(data, result.weights, 504); const ew = simulateEquityCurve(data, benchmarks.equalWeight.weights, 504); const mv = simulateEquityCurve(data, benchmarks.minVariance.weights, 504); const hrpW = computeHRPWeightsArr(data); const hrp = simulateEquityCurve(data, hrpW, 504); const list = selectedTickers.length ? selectedTickers : null; const presetBundles = []; let colorIdx = 0; for (const p of presetOptions) { if (p.objective === "qubo_sa" || p.objective === "vqe") continue; try { const d = generateMarketData(p.nAssets, 504, p.regime, dataSeed, list); const r = runOptimisation(d, { objective: p.objective, wMin: p.minWeight, wMax: p.maxWeight }); const curve = simulateEquityCurve(d, r.weights, 504); const dataKey = `p_${String(p.key).replace(/[^a-zA-Z0-9_]/g, "_")}`; presetBundles.push({ dataKey, name: p.name, curve, color: CHART_COLORS[colorIdx % CHART_COLORS.length] }); colorIdx += 1; } catch { /* preset may fail on tiny universes */ } } const rows = main.map((pt, i) => { const row = { day: pt.day, [activeLabel]: pt.value, "Equal Weight": ew[i]?.value ?? 100, "HRP": hrp[i]?.value ?? 100, "Min Variance": mv[i]?.value ?? 100, }; presetBundles.forEach(({ dataKey, curve }) => { row[dataKey] = curve[i]?.value ?? 100; }); return row; }); const equityPresetLineMeta = presetBundles.map(({ dataKey, name, color }) => ({ dataKey, name, color })); return { equityCurves: rows, equityPresetLineMeta }; }, [data, result.weights, benchmarks, activeLabel, presetOptions, dataSeed, selectedTickers]); /** Lab objectives on current Σ + each catalog preset (own N / regime / bounds), same seed rules as equity overlays. */ const strategyRows = useMemo(() => { if (!data?.assets?.length) return []; const run = (obj) => runOptimisation(data, { objective: obj, wMin: weightMin, wMax: weightMax }); const lab = [ { key: "lab-hybrid", kind: "lab", name: "Hybrid", chartLabel: strategyChartLabel("Hybrid"), profile: "Current lab Σ", ...((r) => ({ sharpe: r.sharpe, ret: r.portReturn * 100, vol: r.portVol * 100, n: r.nActive }))(run("hybrid")) }, { key: "lab-markowitz", kind: "lab", name: "Markowitz", chartLabel: strategyChartLabel("Markowitz"), profile: "Current lab Σ", ...((r) => ({ sharpe: r.sharpe, ret: r.portReturn * 100, vol: r.portVol * 100, n: r.nActive }))(run("markowitz")) }, { key: "lab-hrp", kind: "lab", name: "HRP", chartLabel: strategyChartLabel("HRP"), profile: "Current lab Σ", ...((r) => ({ sharpe: r.sharpe, ret: r.portReturn * 100, vol: r.portVol * 100, n: r.nActive }))(run("hrp")) }, { key: "lab-qubo", kind: "lab", name: "QUBO-SA", chartLabel: strategyChartLabel("QUBO-SA"), profile: "Current lab Σ", ...((r) => ({ sharpe: r.sharpe, ret: r.portReturn * 100, vol: r.portVol * 100, n: r.nActive }))(run("qubo_sa")) }, { key: "lab-vqe", kind: "lab", name: "VQE", chartLabel: strategyChartLabel("VQE"), profile: "Current lab Σ", ...((r) => ({ sharpe: r.sharpe, ret: r.portReturn * 100, vol: r.portVol * 100, n: r.nActive }))(run("vqe")) }, { key: "lab-ew", kind: "lab", name: "Equal Weight", chartLabel: strategyChartLabel("Equal Weight"), profile: "Current lab Σ", ...((r) => ({ sharpe: r.sharpe, ret: r.portReturn * 100, vol: r.portVol * 100, n: r.nActive }))(run("equal_weight")) }, ]; const list = selectedTickers.length ? selectedTickers : null; const presets = []; for (const p of presetOptions) { if (p.objective === "qubo_sa" || p.objective === "vqe") continue; try { const d = generateMarketData(p.nAssets, 504, p.regime, dataSeed, list); const r = runOptimisation(d, { objective: p.objective, wMin: p.minWeight, wMax: p.maxWeight }); presets.push({ key: `preset-${p.key}`, kind: "preset", name: p.name, chartLabel: strategyChartLabel(p.name), profile: `${p.nAssets} assets · ${p.regime}`, sharpe: r.sharpe, ret: r.portReturn * 100, vol: r.portVol * 100, n: r.nActive, }); } catch { /* tiny universe / infeasible */ } } return [...lab, ...presets]; }, [data, weightMin, weightMax, presetOptions, dataSeed, selectedTickers]); const weightSensitivityData = useMemo(() => { if (!data?.assets?.length) return []; return Array.from({ length: 20 }, (_, i) => { const wMax = 0.05 + i * 0.013; const r = runOptimisation(data, { objective, wMin: weightMin, wMax }); return { maxW: `${(wMax * 100).toFixed(0)}%`, sharpe: r.sharpe }; }); }, [data, objective, weightMin]); const sensitivityHeatmap = useMemo(() => { if (!data?.assets?.length) return { rows: [], wSteps: [], maxS: 1, minS: 0 }; const wSteps = [0.10, 0.15, 0.20, 0.25, 0.30]; const objs = [ { value: "markowitz", label: "Markowitz" }, { value: "hrp", label: "HRP" }, { value: "hybrid", label: "Hybrid" }, { value: "min_variance", label: "Min Var" }, ]; const cache = new Map(); const sharpeAt = (objectiveVal, w) => { const k = `${objectiveVal}|${w}`; if (cache.has(k)) return cache.get(k); const sh = runOptimisation(data, { objective: objectiveVal, wMin: weightMin, wMax: w }).sharpe; cache.set(k, sh); return sh; }; let maxS = -Infinity; let minS = Infinity; const rows = objs.map((o) => { const cells = wSteps.map((w) => { const sh = sharpeAt(o.value, w); maxS = Math.max(maxS, sh); minS = Math.min(minS, sh); return { w, sharpe: sh }; }); return { ...o, cells }; }); if (!Number.isFinite(maxS)) maxS = 1; if (!Number.isFinite(minS)) minS = 0; if (minS === maxS) minS -= 0.01; return { rows, wSteps, maxS, minS }; }, [data, weightMin]); /** Closest heatmap column index to sidebar max weight (for highlight). */ const sensitivityHeatmapColIdx = useMemo(() => { const steps = sensitivityHeatmap.wSteps; if (!steps?.length) return 0; let best = 0; let bestD = Infinity; steps.forEach((w, i) => { const d = Math.abs(w - weightMax); if (d < bestD) { bestD = d; best = i; } }); return best; }, [sensitivityHeatmap.wSteps, weightMax]); const portDailyReturnsPct = useMemo(() => { if (!data?.assets?.length || !result?.weights?.length) return []; const T = data.assets[0]?.returns?.length || 0; const out = []; for (let d = 0; d < T; d++) { let s = 0; for (let i = 0; i < data.assets.length; i++) s += (result.weights[i] || 0) * (data.assets[i].returns[d] || 0); out.push(s * 100); } return out; }, [data, result.weights]); const returnPercentiles = useMemo(() => empiricalPercentiles(portDailyReturnsPct), [portDailyReturnsPct]); const pnlHistogram = useMemo(() => { const arr = portDailyReturnsPct; if (!arr.length) return []; let mn = Math.min(...arr); let mx = Math.max(...arr); if (mx <= mn) { mn -= 0.01; mx += 0.01; } const bins = 18; const w = (mx - mn) / bins; const counts = Array(bins).fill(0); arr.forEach((x) => { let i = Math.floor((x - mn) / w); if (i >= bins) i = bins - 1; if (i < 0) i = 0; counts[i] += 1; }); const n = arr.length; const mean = arr.reduce((a, b) => a + b, 0) / n; const variance = arr.reduce((s, x) => s + (x - mean) ** 2, 0) / Math.max(n - 1, 1); const sigma = Math.sqrt(Math.max(variance, 1e-12)); return counts.map((c, i) => { const lo = mn + i * w; const hi = mn + (i + 1) * w; const mid = (lo + hi) / 2; const pBin = stdNormalCDF((hi - mean) / sigma) - stdNormalCDF((lo - mean) / sigma); const normalCount = n * pBin; return { bin: fmtAxis2(mid), mid, count: c, normalCount, lo, hi }; }); }, [portDailyReturnsPct]); /** Row/column order: sector then name (readability vs raw matrix order). */ const corrAssetOrder = useMemo(() => { if (!data?.assets?.length) return []; return data.assets .map((a, i) => ({ i, sector: a.sector || "", name: a.name })) .sort((a, b) => { const sc = a.sector.localeCompare(b.sector); if (sc !== 0) return sc; return a.name.localeCompare(b.name); }) .map((x) => x.i); }, [data]); const marginalRiskRows = useMemo(() => { if (!data?.assets?.length || !result?.weights?.length) return []; try { const contrib = calculateRiskContributions(result.weights, data); return data.assets .map((a, i) => ({ name: a.name, mrcPct: (contrib[i] != null ? contrib[i] : 0) * 100 })) .filter((_, i) => (result.weights[i] || 0) > 0.005) .sort((a, b) => Math.abs(b.mrcPct) - Math.abs(a.mrcPct)) .slice(0, 15); } catch { return []; } }, [data, result]); const equityExtras = useMemo(() => { if (!equityCurves.length) return []; let peak = -Infinity; return equityCurves.map((row, idx) => { const vStrat = row[activeLabel]; peak = Math.max(peak, vStrat); const dd = peak > 0 ? ((vStrat - peak) / peak) * 100 : 0; const vEw = row["Equal Weight"]; let rv = null; const win = 20; if (idx >= win) { const rets = []; for (let k = idx - win + 1; k <= idx; k++) { const prev = equityCurves[k - 1][activeLabel]; const cur = equityCurves[k][activeLabel]; if (prev) rets.push((cur - prev) / prev); } const m = rets.reduce((a, b) => a + b, 0) / (rets.length || 1); const vr = rets.reduce((a, b) => a + (b - m) ** 2, 0) / (rets.length || 1); rv = Math.sqrt(Math.max(0, vr)) * Math.sqrt(252) * 100; } return { ...row, _dd: dd, _rv: rv, _ew: vEw }; }); }, [equityCurves, activeLabel]); const equityMeta = useMemo(() => { if (!equityExtras.length) return { maxDrawdownPct: 0, maxDdDay: 0 }; let best = 0; let day = 0; equityExtras.forEach((row) => { if (row._dd < best) { best = row._dd; day = row.day; } }); return { maxDrawdownPct: best, maxDdDay: day }; }, [equityExtras]); const universeSizeData = useMemo(() => { const list = selectedTickers.length ? selectedTickers : null; const candidates = [5, 10, 15, 20, 25, 30]; const ns = list ? [...new Set(candidates.filter((x) => x <= list.length).concat(list.length >= 2 ? [list.length] : []))].sort((a, b) => a - b) : candidates; const filtered = ns.filter((n) => n >= 2); if (list && list.length < 2) return []; return filtered.map((n) => { const d = generateMarketData(n, 504, regime, dataSeed, list); return { n: `N=${n}`, hybrid: runOptimisation(d, { objective: "hybrid", wMin: weightMin, wMax: weightMax }).sharpe, markowitz: runOptimisation(d, { objective: "markowitz", wMin: weightMin, wMax: weightMax }).sharpe, hrp: runOptimisation(d, { objective: "hrp", wMin: weightMin, wMax: weightMax }).sharpe, }; }); }, [regime, dataSeed, weightMin, weightMax, selectedTickers]); const bestBenchSharpe = Math.max(benchmarks.equalWeight.sharpe, benchmarks.minVariance.sharpe, benchmarks.riskParity.sharpe, benchmarks.maxSharpe.sharpe, 0.001); const sharpeImprovement = ((result.sharpe / bestBenchSharpe) - 1) * 100; const kpiBenchmarks = useMemo(() => { const ew = benchmarks.equalWeight; const mv = benchmarks.minVariance; const ms = benchmarks.maxSharpe; const maxW = result.weights?.length ? Math.max(...result.weights) * 100 : 0; const n = data?.assets?.length ?? 0; const retVsEwPp = (result.portReturn - ew.portReturn) * 100; const volVsMvPp = (result.portVol - mv.portVol) * 100; const sharpeTag = result.sharpe >= 1.5 ? "Strong" : result.sharpe >= 0.75 ? "Moderate" : "Lean"; const sharpeTagTone = result.sharpe >= 1.5 ? "positive" : result.sharpe >= 0.75 ? "warning" : "neutral"; const tailRatio = riskMetrics.cvar > 1e-6 ? riskMetrics.var95 / riskMetrics.cvar : null; const returnTagTone = retVsEwPp >= 0.25 ? "positive" : retVsEwPp <= -0.25 ? "negative" : "neutral"; const volTagTone = volVsMvPp <= -0.25 ? "positive" : volVsMvPp >= 0.25 ? "warning" : "neutral"; const concentrationTag = maxW > 35 ? "Concentrated" : maxW > 20 ? "Balanced" : "Diversified"; const concentrationTone = maxW > 35 ? "warning" : "positive"; const varTag = riskMetrics.var95 > 1.2 ? "Tail risk" : riskMetrics.var95 > 0.6 ? "Moderate" : "Contained"; const varTone = riskMetrics.var95 > 1.2 ? "negative" : riskMetrics.var95 > 0.6 ? "warning" : "positive"; return { ewSharpe: ew.sharpe, msSharpe: ms.sharpe, retVsEwPp, volVsMvPp, mvVolPct: mv.portVol * 100, maxW, n, sharpeTag, sharpeTagTone, tailRatio, returnTagTone, volTagTone, concentrationTag, concentrationTone, varTag, varTone, }; }, [benchmarks, result, riskMetrics, data]); const axisStyle = { fontSize: 11, fill: t.textMuted, fontFamily: FONT.mono }; const gridProps = { strokeDasharray: "3 3", stroke: t.border, vertical: false }; return (
{/* ── Header ── */}
Q

Quantum Portfolio Lab

Hybrid Optimization Dashboard

{/* ── Sidebar (configuration & API controls — not inside #qpl-main) ── */} {/* ── Main Content ── */}
{marketMode === "live" && !isLiveLoaded && ( Apply universe to load live prices into the lab. )}
{isApiMode && (
Backend Results from API server
)} {/* ── Portfolio Tab — funded portfolio simulation ── */} {activeTab === "portfolio" && (() => { const fmtDollar = (v) => v >= 1e6 ? `$${(v / 1e6).toFixed(2)}M` : v >= 1e3 ? `$${(v / 1e3).toFixed(1)}K` : `$${v.toFixed(0)}`; const fmtDollarFull = (v) => `$${v.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; const pnlColor = (v) => v > 0 ? t.green : v < 0 ? t.red : t.textDim; const regimeLabel = REGIMES.find((r) => r.key === regime)?.label ?? regime; const sortedHoldings = [...holdings].sort((a, b) => { const dir = holdingsSort.asc ? 1 : -1; const col = holdingsSort.col; if (col === "name") return dir * a.name.localeCompare(b.name); if (col === "sector") return dir * a.sector.localeCompare(b.sector); return dir * ((a[col] ?? 0) - (b[col] ?? 0)); }); const posMap = {}; fundedPortfolio.finalPositions.forEach((p) => { posMap[p.name] = p; }); const thStyle = (col) => ({ padding: "8px 10px", textAlign: col === "name" || col === "sector" ? "left" : "right", borderBottom: `1px solid ${t.border}`, color: holdingsSort.col === col ? t.accent : t.textDim, fontSize: 10, textTransform: "uppercase", letterSpacing: "0.04em", cursor: "pointer", userSelect: "none", }); const toggleSort = (col) => setHoldingsSort((prev) => prev.col === col ? { col, asc: !prev.asc } : { col, asc: col === "name" || col === "sector" } ); const sortArrow = (col) => holdingsSort.col === col ? (holdingsSort.asc ? " ▲" : " ▼") : ""; const exportCSV = () => { const header = "Name,Sector,Weight%,DollarAlloc,PnL,PnL%\n"; const rows = sortedHoldings.map((h) => { const p = posMap[h.name]; return `${h.name},${h.sector},${(h.weight * 100).toFixed(2)},${(h.weight * notional).toFixed(2)},${p?.pnl?.toFixed(2) ?? 0},${p?.pnlPct?.toFixed(2) ?? 0}`; }).join("\n"); const blob = new Blob([header + rows], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `portfolio_${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); }; const exportJSON = () => { const payload = { notional, objective, regime, weightMin, weightMax, summary: fundedPortfolio.summary, holdings: sortedHoldings.map((h) => { const p = posMap[h.name]; return { name: h.name, sector: h.sector, weight: h.weight, dollarAlloc: h.weight * notional, pnl: p?.pnl ?? 0, pnlPct: p?.pnlPct ?? 0 }; }), }; void navigator.clipboard?.writeText(JSON.stringify(payload, null, 2)).catch(() => {}); }; return (
{/* ── 1. Intro (compact) + Notional (inline) ── */}
Portfolio book
Notional
$ { const v = parseFloat(e.target.value); if (v > 0) setNotional(v); }} style={{ flex: 1, fontSize: 16, fontFamily: FONT.mono, fontWeight: 600, padding: "6px 10px", borderRadius: 6, border: `1px solid ${t.border}`, background: t.bg, color: t.text, outline: "none", maxWidth: 180 }} />
{[10000, 50000, 100000, 500000, 1000000].map((v) => ( ))}
{/* ── 2. Funded KPIs ── */}
= 0 ? "+" : ""}${fmtDollarFull(fundedPortfolio.summary.totalPnl)}`} color={pnlColor(fundedPortfolio.summary.totalPnl)} description="Mark-to-market vs starting notional." /> = 0 ? "+" : ""}${fundedPortfolio.summary.totalReturnPct.toFixed(2)}`} unit="%" color={pnlColor(fundedPortfolio.summary.totalReturnPct)} description="Over the loaded return horizon." />
{/* ── 3. Provenance ── */} Optimizer provenance
{[ { label: "Objective", val: activeLabel }, { label: "Data source", val: marketMode === "live" && isLiveLoaded ? "Live market" : `Synthetic · ${regimeLabel}` }, { label: "Weight bounds", val: `${(weightMin * 100).toFixed(1)}% – ${(weightMax * 100).toFixed(1)}%` }, { label: "Cardinality", val: cardinality ? String(cardinality) : "Uncapped" }, { label: "Seed", val: String(dataSeed) }, { label: "Universe", val: `${data?.assets?.length ?? nAssets} names` }, ].map((c) => (
{c.label}
{c.val}
))}
{result.stage_info && (
Pipeline stages
{result.stage_info.stage1_screened_count &&
Screen: {result.stage_info.stage1_screened_count} candidates
} {result.stage_info.stage2_selected_names &&
Select: {result.stage_info.stage2_selected_names.join(", ")}
} {result.stage_info.stage3_sharpe !== undefined &&
Sharpe: {result.stage_info.stage3_sharpe?.toFixed(3)}
}
)}
{/* ── 4. Positions (table is single source of truth; bar chart inside vs-Cap column) ── */}
Dollar holdings
{sortedHoldings.length > 0 ? sortedHoldings.map((h, i) => { const pos = posMap[h.name]; const alloc = h.weight * notional; const pnl = pos?.pnl ?? 0; const pnlPct = pos?.pnlPct ?? 0; return ( e.currentTarget.style.background = t.surfaceLight} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}> ); }) : ( )}
# toggleSort("name")} style={thStyle("name")}>Name{sortArrow("name")} toggleSort("sector")} style={thStyle("sector")}>Sector{sortArrow("sector")} toggleSort("weight")} style={thStyle("weight")}>Weight{sortArrow("weight")} Alloc P&L P&L % vs Cap
{i + 1} {h.name} {h.sector} {(h.weight * 100).toFixed(2)}% {fmtDollarFull(alloc)} {pnl >= 0 ? "+" : ""}{fmtDollarFull(pnl)} {pnlPct >= 0 ? "+" : ""}{pnlPct.toFixed(2)}%
= weightMax * 0.98 ? t.accentWarm : CHART_COLORS[i % CHART_COLORS.length], borderRadius: 2 }} />
No holdings data
{/* ── 5. Diagnostics (concentration + constraints — one band) ── */} Diagnostics
{[ { label: "HHI", val: concentrationMetrics.hhi.toFixed(4), sub: "0 = diversified", binding: false }, { label: "Effective N", val: concentrationMetrics.effectiveN.toFixed(1), sub: "1/HHI", binding: false }, { label: "Top-5 weight", val: `${(concentrationMetrics.top5 * 100).toFixed(1)}%`, sub: "sum of 5 largest", binding: false }, { label: "Max weight", val: `${(concentrationMetrics.maxW * 100).toFixed(1)}%`, sub: `${(weightMax * 100).toFixed(1)}% cap`, binding: concentrationMetrics.maxW >= weightMax * 0.98 }, { label: "Min weight", val: `${(concentrationMetrics.minW * 100).toFixed(1)}%`, sub: `${(weightMin * 100).toFixed(1)}% floor`, binding: concentrationMetrics.minW <= weightMin * 1.05 }, { label: "Active / N", val: `${concentrationMetrics.nActive} / ${data?.assets?.length ?? nAssets}`, sub: cardinality ? `${cardinality} cap` : "uncapped", binding: cardinality ? concentrationMetrics.nActive >= cardinality : false }, { label: "Turnover", val: `${(turnoverLimit * 100).toFixed(0)}%`, sub: "per rebalance", binding: false }, ].map((c) => (
{c.label}
{c.val}
{c.sub}{c.binding ? " · binding" : ""}
))}
{/* ── 6. Methodology + Universe ── */} Methodology
Assumptions & limitations

Simulation. Each position is initialized at weight × notional and marked daily using the same per-asset return series as the rest of the lab. No transaction costs, slippage, or taxes. Not investment advice.

Distinct from other tabs. Performance shows cumulative portfolio value vs benchmarks on a normalized scale (rescaled to your notional). Risk adds VaR, correlation, and stress views on the same book.

Universe & market data
); })()} {/* ── Performance Tab ── */} {activeTab === "performance" && (
bestBenchSharpe ? t.green : t.accent} delta={sharpeImprovement} description="Risk-adjusted return (μ/σ)" tag={kpiBenchmarks.sharpeTag} tagTone={kpiBenchmarks.sharpeTagTone} progress={Math.min(1, result.sharpe / 2.5)} progressCaption="Scale vs 2.5 reference Sharpe (illustrative)" insight={`Benchmarks on this Σ: equal-weight ${kpiBenchmarks.ewSharpe.toFixed(2)} · heuristic max-Sharpe ${kpiBenchmarks.msSharpe.toFixed(2)} · best of four rule-based ${bestBenchSharpe.toFixed(2)}.`} formula="Sharpe ≈ (annualized portfolio return) / (annualized portfolio volatility)" detail="Higher is better for the same risk budget; not comparable across different return horizons without adjustment." benchmarkNote={`Best benchmark Sharpe in this run: ${bestBenchSharpe.toFixed(3)}`} /> = 0 ? "Above EW" : "Below EW"} tagTone={kpiBenchmarks.returnTagTone} progress={Math.min(1, Math.max(0, result.portReturn / 0.22))} progressCaption="Scale vs ~22% ann. return (illustrative cap)" insight={`Δ vs equal-weight (same covariance): ${kpiBenchmarks.retVsEwPp >= 0 ? "+" : ""}${kpiBenchmarks.retVsEwPp.toFixed(2)} pp · EW ann. ${(benchmarks.equalWeight.portReturn * 100).toFixed(2)}%.`} formula="μ′w using asset expected returns and weights" />
Purpose. This is a cross-sectional snapshot: every name plotted by risk (horizontal) vs reward (vertical). Larger bubbles are heavier positions. Use it to see whether you are earning return in volatile names (right side), hiding in low-vol names (left), or leaving attractive points (upper-left vs lower-right tradeoffs) out of the portfolio entirely. )} > Risk–return map {riskReturnScatter.length > 0 ? ( { if (!active || !payload?.length) return null; const d = payload[0]?.payload; if (!d) return null; return (
{d.name} ({d.sector})
Return: {d.y.toFixed(2)}% | Vol: {d.x.toFixed(2)}%
Weight: {d.z.toFixed(2)}%
); }} /> !d.inPortfolio)} fill={t.textDim} fillOpacity={0.25}>{riskReturnScatter.filter(d => !d.inPortfolio).map((_, i) => )} d.inPortfolio)} fill={t.accent}>{riskReturnScatter.filter(d => d.inPortfolio).map((d, i) => )}
) :
No data
}
100 ? `$${(notional / 1000).toFixed(0)}K` : "$100"} start · same horizon for every series · brush to zoom`} explainer={( <> How to read this. The filled area is your currently selected objective on the lab matrix in the sidebar. The three dashed lines are rule-based benchmarks on that same covariance (equal weight, HRP, min-variance). Each colored preset line replays a sidebar preset: its own N, regime, min/max weight, and objective, with the same seed (and ticker list if you set one). Quantum objectives (QUBO / VQE) are omitted from the overlay so the chart stays responsive. )} > Cumulative performance {equityExtras.length > 0 ? ( <>
Portfolio value ($)
String(Math.round(v))} axisLine={{ stroke: t.border }} tickLine={false} minTickGap={24} height={32} /> { const scaled = v * notional / 100; return scaled >= 1e6 ? `$${(scaled / 1e6).toFixed(1)}M` : scaled >= 1e3 ? `$${(scaled / 1e3).toFixed(0)}K` : `$${scaled.toFixed(0)}`; }} axisLine={{ stroke: t.border }} tickLine={false} domain={["auto", "auto"]} width={68} /> } /> {equityMeta.maxDdDay > 0 && !equitySeriesHidden[activeLabel] && ( )} {!equitySeriesHidden[activeLabel] && ( )} {!equitySeriesHidden["Equal Weight"] && ( )} {!equitySeriesHidden.HRP && ( )} {!equitySeriesHidden["Min Variance"] && ( )} {equityPresetLineMeta.map((m) => !equitySeriesHidden[m.dataKey] ? ( ) : null, )} String(Math.round(v))} />
Trading days
) :
No data
}
Purpose. Compare Sharpe, annualized return, and volatility for every lab objective on the current covariance, then each sidebar preset with its own N, regime, and bounds (same seed rules as the equity overlay). Teal = Sharpe, green = return %, amber = vol %. The table matches the chart; Sharpe uses a light heat tint vs the column max. )} > Strategy comparison {strategyRows.length > 0 && (() => { const maxSharpe = Math.max(...strategyRows.map((x) => x.sharpe), 0); const minSharpe = Math.min(...strategyRows.map((x) => x.sharpe), 0); const sharpeSpan = Math.max(maxSharpe - minSharpe, 1e-9); const labRows = strategyRows.filter((r) => r.kind === "lab"); const presetRows = strategyRows.filter((r) => r.kind === "preset"); const maxLabelChars = strategyRows.reduce((m, r) => Math.max(m, (r.chartLabel || "").length), 0); const yAxisWidth = Math.min(200, Math.max(92, 8 + maxLabelChars * 6.5)); const chartH = Math.min(640, 48 + strategyRows.length * 46); const renderRow = (b) => { const isBest = b.sharpe >= maxSharpe - 1e-6; const sn = (b.sharpe - minSharpe) / sharpeSpan; const sharpeBg = `rgba(45, 212, 191, ${0.06 + sn * 0.18})`; return ( { e.currentTarget.style.background = t.surfaceLight; }} onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }} > {b.name} {isBest && ( )} {b.profile} {b.sharpe.toFixed(3)} {b.ret.toFixed(2)}% {b.vol.toFixed(2)}% {b.n} ); }; return ( <>
Sharpe Return % Vol %
8 ? "10%" : "16%"} barGap={2} > } />
{["Strategy", "Profile", "Sharpe", "Return", "Volatility", "Positions"].map((h) => ( ))} {labRows.map(renderRow)} {presetRows.length > 0 && ( )} {presetRows.map(renderRow)}
{h}
Lab objectives · same Σ as the sidebar
Sidebar presets · re-simulated (catalog N / regime / bounds)
); })()}
)} {/* ── Risk Tab ── */} {activeTab === "risk" && (
{isApiMode && apiResult && (apiResult.risk_metrics || apiResult.stage_info) && ( Backend risk & pipeline
{apiResult.risk_metrics?.var_95 != null && (
risk_metrics.var_95
{(Number(apiResult.risk_metrics.var_95) * 100).toFixed(2)}%

95% VaR (historical), fraction of portfolio in API response — shown as %.

)} {apiResult.risk_metrics?.cvar != null && (
risk_metrics.cvar
{(Number(apiResult.risk_metrics.cvar) * 100).toFixed(2)}%

Conditional VaR / expected shortfall (ES).

)} {apiResult.stage_info?.stage1_screened_count != null && (
stage_info.stage1_screened_count
{apiResult.stage_info.stage1_screened_count}

Candidates after Stage 1 screen.

)} {apiResult.stage_info?.stage2_selected_names?.length > 0 && (
stage_info.stage2_selected_names
{apiResult.stage_info.stage2_selected_names.join(", ")}
)} {apiResult.stage_info?.stage3_sharpe != null && (
stage_info.stage3_sharpe
{Number(apiResult.stage_info.stage3_sharpe).toFixed(3)}

Sharpe after Stage 3 pipeline.

)}
Raw JSON (debug)
                      {JSON.stringify({ risk_metrics: apiResult.risk_metrics, stage_info: apiResult.stage_info }, null, 2)}
                    
)}
= 0 ? "+" : ""}${kpiBenchmarks.volVsMvPp.toFixed(2)} pp · MV portfolio ${kpiBenchmarks.mvVolPct.toFixed(2)}%.`} formula="√(w′Σw) from covariance implied by correlations and volatilities" /> 0 ? result.nActive / kpiBenchmarks.n : 0} progressCaption="Names active / tradable universe" insight={`Largest holding ≈ ${kpiBenchmarks.maxW.toFixed(1)}% of portfolio · ${kpiBenchmarks.n} names in current matrix.`} detail="Count of holdings above the 0.5% weight floor in the table." />
Purpose. Sector view answers where risk is parked by industry: a balanced book shows several slices; one or two fat slices mean sector concentration (fine if deliberate). Hover or use the legend to read exact percentages—compare to your mandate or to a benchmark story you have in mind. )} > Sector breakdown {sectorData.length > 0 ? ( {sectorData.map((_, i) => )} } /> ) :
No sector data
}
Value at Risk
{isApiMode ? "Source: backend risk_metrics" : "Source: lab Monte Carlo (see methodology)"} {marketMode === "live" && isLiveLoaded ? "Universe: live market window" : "Universe: simulated lab data"}
{[{ label: "Daily VaR", value: riskMetrics.var95, color: t.accentWarm, sub: "of portfolio" }, { label: "Daily CVaR (ES)", value: riskMetrics.cvar, color: t.red, sub: "expected shortfall" }].map((m) => (
{m.label}
{m.value.toFixed(2)}%
{m.sub}
))}
On $1M notion: VaR ≈ ${(riskMetrics.var95 * 10000).toFixed(0)} | CVaR ≈ ${(riskMetrics.cvar * 10000).toFixed(0)} daily
Methodology & disclaimer

KPI VaR/CVaR above: {isApiMode ? ( <>values come from the last optimize response (risk_metrics.var_95, risk_metrics.cvar), scaled to % for display. ) : ( <>computed in the lab by computeVaR: 2,000 Monte Carlo draws that bootstrap random days from each asset's return path, sort losses, take the 95% quantile for VaR and the tail mean for CVaR (see web/src/lib/simulationEngine.js). )}

Not investment advice. Does not include transaction costs, liquidity, or model risk. Horizon is one trading day on the loaded return series.

The histogram below uses actual realized daily portfolio returns (%); the dashed normal curve is a Gaussian fit with the same sample mean and variance — compare visually to assess tail heaviness.

Asset correlation

Hover a cell for details; each cell has a native tooltip (title) with pair names and ρ for touch readers.

ρ scale
{Array.from({ length: 21 }, (_, k) => { const u = k / 20; const rho = -1 + u * 2; const bg = `rgb(${Math.round(40 + u * 120)},${Math.round(60 + (1 - u) * 80)},${Math.round(80 + u * 100)})`; return
; })}
−1 0 +1
{data?.assets?.length > 0 && data.corr?.length && corrAssetOrder.length ? ( ))} {corrAssetOrder.map((oi) => { const ai = data.assets[oi]; return ( {corrAssetOrder.map((oj) => { const aj = data.assets[oj]; const rho = data.corr[oi]?.[oj] ?? 0; const u = (rho + 1) / 2; const bg = `rgb(${Math.round(40 + u * 120)},${Math.round(60 + (1 - u) * 80)},${Math.round(80 + u * 100)})`; const hover = corrHover?.i === oi && corrHover?.j === oj; const title = `${ai.name} vs ${aj.name}: ρ=${fmtAxis2(rho)}${ai.sector === aj.sector ? " · same sector" : ""}`; return ( ); })} ); })}
Pearson correlation on lab covariance; order groups by sector for readability.
{corrAssetOrder.map((j) => ( {data.assets[j].name.slice(0, 4)}
{ai.name} setCorrHover({ i: oi, j: oj, ai: ai.name, aj: aj.name, rho, same: ai.sector === aj.sector })} onMouseLeave={() => setCorrHover(null)} style={{ padding: 4, textAlign: "center", background: bg, color: t.text, outline: hover ? `2px solid ${t.accent}` : "none", cursor: "default", }} > {fmtAxis2(rho)}
) :
No correlation data
} {corrHover && (
{corrHover.ai} vs {corrHover.aj}: ρ = {fmtAxis2(corrHover.rho)} · {corrHover.same ? "same sector" : "different sector"}
)}
Portfolio P&L distribution {returnPercentiles && (
P5 {fmtAxis2(returnPercentiles.p5)}% P25 {fmtAxis2(returnPercentiles.p25)}% Median {fmtAxis2(returnPercentiles.p50)}% P75 {fmtAxis2(returnPercentiles.p75)}% P95 {fmtAxis2(returnPercentiles.p95)}%
)} {pnlHistogram.length > 0 ? ( } /> {returnPercentiles && ( <> )} ) :
No return series
}

Bars: empirical daily portfolio return counts. Dashed line: expected counts per bin if returns were Gaussian with the sample mean and variance. Vertical lines: KPI VaR/CVaR (from the card above) and empirical percentiles.

Style proxy (heuristic)

These six spokes are deterministic functions of Sharpe, return, vol, active names, and max weight — not a regression on factor returns. Use the chart to see shape vs equal-weight on identical data; the table spells out each formula and numeric value.

{styleProxyRadar.rows.length > 0 ? ( <> { if (!active || !payload?.length) return null; const row = payload[0]?.payload; if (!row) return null; return (
{row.factor}
Portfolio: {Number(row.portfolio).toFixed(3)}
Eq. weight: {Number(row.benchmark).toFixed(3)}
{row.formula}
); }} />
{styleProxyRadar.rows.map((r) => { const delta = r.portfolio - r.benchmark; return ( ); })}
Spoke Portfolio Eq. weight Δ Formula
{r.factor} {r.portfolio.toFixed(3)} {r.benchmark.toFixed(3)} = 0 ? t.green : t.red }}>{delta >= 0 ? "+" : ""}{delta.toFixed(3)} {r.formula}
) : (
Run an optimization to compare style proxy vs equal-weight.
)}
Stress tests & optimization path

Not a historical path simulation. Each card applies a fixed scenario depth s to a single number from your current book: annualized portfolio vol σ_p from w* on the lab covariance Σ.

{[ { step: "1", title: "Σ, regime", body: "Lab correlation + vols (or live window) define feasible risk." }, { step: "2", title: "Objective", body: `Sidebar: ${activeLabel} · caps & cardinality shape the search.` }, { step: "3", title: "Optimizer → w*", body: "Solver returns weights; hybrid/QUBO/classical paths differ in how subsets are chosen." }, { step: "4", title: "σ_p(w*)", body: `Current book: ${(result.portVol * 100).toFixed(2)}% ann. vol (drives stress scale).` }, { step: "5", title: "Loss proxy", body: "Multiply scenario depth s by g(σ_p) — see formula below." }, ].map((b) => (
Step {b.step}
{b.title}
{b.body}
))}
Where algorithms plug in

{stressPipelineAlgorithmNote(objective)}

Affine map (same for all scenarios): impact% = s × (0.5 + 3·σ_p) × 100,  σ_p = {result.portVol?.toFixed(4) ?? "—"} (annualized),  s ∈ {"{"}-0.50, -0.34, -0.25, -0.09{"}"} Reference (same formula, equal-weight on Σ): σ_p,ew = {(benchmarks.equalWeight.portVol * 100).toFixed(2)}% — GFC proxy = {(STRESS_SCENARIOS[0].shock * (0.5 + (benchmarks.equalWeight.portVol || 0) * 3) * 100).toFixed(2)}% vs your book {(STRESS_SCENARIOS[0].shock * (0.5 + (result.portVol || 0) * 3) * 100).toFixed(2)}%.
{STRESS_SCENARIOS.map((s) => { const gSigma = 0.5 + (result.portVol || 0) * 3; const impact = s.shock * gSigma * 100; return (
{s.name}
s = {s.shock.toFixed(2)} · g(σ_p) = {gSigma.toFixed(3)}
{s.mechanism}
{impact.toFixed(2)}%
Proxy portfolio loss (one-day style)
); })}

Interpretation. Lower |σ_p| from diversification or tighter caps shrinks the scaled loss for the same s. The optimizer’s job is to move w* within constraints — stress does not re-run the solver; it post-processes σ_p(w*).

Source. Names come from the lab universe (data.assets) with current portfolio weight > 0.5% after your last optimization. We keep the top 15 by absolute marginal contribution using calculateRiskContributions(result.weights, data) on the same covariance as the correlation heatmap. )} > Marginal risk contribution

Why this chart. VaR and stress above summarize risk at portfolio level through σp(w*). MRC splits that total volatility into name-level pieces so you see which holdings move σp at the margin — the natural complement to the pairwise ρ matrix (structure) and the aggregate loss proxies.

Tab flow.{" "} Correlation {" → "} σp & return distribution {" → "} VaR / stress {" → "} MRC (who drives σp) {" — all use the same optimized "} w* {" on this Σ unless you re-run the lab."}

{marginalRiskRows.length > 0 ? ( } /> ) :
No positions for risk split
}
)} {/* ── Sensitivity Tab — scientist bench + optional legacy heatmaps ── */} {activeTab === "sensitivity" && (

Scientist bench — tune spec, run client or API optimizers, edit weights, inspect metrics on the current Σ. Use Sync from sidebar in Spec to copy sidebar defaults.

Advanced: legacy sensitivity heatmaps
Heatmap focus: sweeps objective × w_max on the same Σ — pair with Risk and Performance for full context.
`${(w * 100).toFixed(0)}%`).join(" · ")} · w_min = ${(weightMin * 100).toFixed(1)}% (sidebar)`} explainer={( <> What is swept. Each cell is one runOptimisation on lab data with that objective and w_max; w_min comes from the sidebar. Current objective: {activeLabel} · max weight: {(weightMax * 100).toFixed(0)}%. The highlighted column is closest to your sidebar cap. Δ vs current Sharpe appears on the row that matches your objective (vs last optimization). )} > Parameter heatmap
Sharpe scale
{Array.from({ length: 24 }, (_, k) => { const u = k / 23; const { minS, maxS } = sensitivityHeatmap; const v = minS + u * (maxS - minS); const uCell = maxS > minS ? (v - minS) / (maxS - minS) : 0.5; const bg = `rgb(${Math.round(30 + uCell * 140)},${Math.round(50 + uCell * 100)},${Math.round(90 + uCell * 80)})`; return
; })}
{sensitivityHeatmap.minS.toFixed(3)} {sensitivityHeatmap.maxS.toFixed(3)}
{sensitivityHeatmap.wSteps.map((w, wi) => { const isCol = wi === sensitivityHeatmapColIdx; return ( ); })} {sensitivityHeatmap.rows.map((row) => { const rowIsCurrentObjective = row.value === objective; return ( {row.cells.map((cell, wi) => { const { minS, maxS } = sensitivityHeatmap; const u = maxS > minS ? (cell.sharpe - minS) / (maxS - minS) : 0.5; const bg = `rgb(${Math.round(30 + u * 140)},${Math.round(50 + u * 100)},${Math.round(90 + u * 80)})`; const isCol = wi === sensitivityHeatmapColIdx; const delta = rowIsCurrentObjective ? cell.sharpe - result.sharpe : null; return ( ); })} ); })}
Objective max {(w * 100).toFixed(0)}%{isCol ? " · near sidebar" : ""}
{row.label} {rowIsCurrentObjective && ( ← sidebar objective )}
{cell.sharpe.toFixed(3)}
{rowIsCurrentObjective && delta != null && (
= 0 ? t.green : t.red, marginTop: 2 }}> Δ {delta >= 0 ? "+" : ""}{delta.toFixed(3)} vs run
)}

If a row is flat across columns, the cap may not bind for that objective on this universe — the optimum is unchanged across the scanned w_max range.

How it connects. Fixes {activeLabel} and varies only the cap. The vertical line marks your current max weight. Compare to the heatmap row for the same objective. )} > Weight sensitivity } /> Purpose. How Sharpe scales when universe cardinality changes. Complements the heatmap (cap sensitivity) and Performance presets that use different N. )} > Universe size impact {universeSizeData.length === 0 ? (
Select at least two tickers to sweep universe size against your custom list.
) : ( } /> )}
)}
); }