/* global React */ /* Vault Control — Monitor screen: health/lifecycle digest. */ const { Card: MonCard } = window.BraveAlphaCapitalDesignSystem_c4b179; const { Stat: MonStat, InfoDot: MonInfo, FMT: MonF, toneOf: monTone, Msg: MonMsg, Icon: MonIcon } = window; const MonVC = window.VC; // ---- Lifecycle tags ----------------------------------------------------------- const STATUS_COLORS = { active: { c: "var(--green)", bg: "rgba(31,122,77,.08)" }, watch: { c: "var(--gold-ink)", bg: "rgba(176,136,60,.10)" }, retire: { c: "var(--red)", bg: "rgba(178,58,58,.08)" }, // pre-054 tags, kept so a historical lifecycle event still renders in colour incubation: { c: "#3d7ea6", bg: "rgba(61,126,166,.10)" }, probation: { c: "var(--gold-ink)", bg: "rgba(176,136,60,.10)" }, shadow: { c: "var(--muted)", bg: "var(--paper)" }, archived: { c: "var(--ink)", bg: "var(--surface)" }, deprecated: { c: "var(--red)", bg: "rgba(178,58,58,.08)" }, }; function StatusChip({ status }) { const cfg = STATUS_COLORS[status] || STATUS_COLORS.watch; return {status}; } function SeverityDot({ severity }) { const c = severity === "critical" ? "var(--red)" : severity === "warning" ? "var(--gold-ink)" : "var(--green)"; return ; } function HealthBadge({ status }) { const c = status === "ok" ? "var(--green)" : status === "watch" ? "var(--gold-ink)" : status === "retire" ? "var(--red)" : "var(--muted)"; return {status?.toUpperCase() || "—"}; } // ---- Decay breakdown (click a row to see why the score is what it is) -------- function BreakdownStat({ label, value, detail, tone }) { return (
{label}
{detail &&
{detail}
}
{value}
); } function BreakdownGroup({ title, children }) { return (
{title}
{children}
); } // ---- Strategy logic + basket membership (registry-derived, not health) -------------- // signal_params is genuinely heterogeneous across registration rounds: some rows carry a // single entry/exit DSL string, some split entry_long/exit_long/entry_short/exit_short, // the oldest/DCX-tracked rows carry no DSL at all (just regime/sizing notes). Render // whichever shape is actually on file rather than assuming one, so nothing silently // disappears just because a strategy predates a naming convention. function LogicLine({ label, value }) { if (!value) return null; return (
{label}
{value}
); } function MetaChip({ children }) { return {children}; } const LOGIC_RISK_KEYS = ["hard_stop", "hard_stop_pct", "sl_pct", "tp_pct", "sl_roi_pct", "tp_roi_pct", "stop", "exit_target", "exit_trail", "exit_scheme", "fixed_bracket"]; const LOGIC_META_KEYS = ["tier", "source", "regime", "deploy_size", "sized_on", "support_tf"]; function StrategyLogicPanel({ r }) { const sp = r.signal_params || {}; // strategy_registry carries the DSL in dedicated columns AND (usually) in // signal_params. All 263 rows have the columns; 31 have ONLY the columns, so // reading signal_params alone renders "no logic on file" for those. Prefer the // column and fall back — the same precedence sgExitClauses already uses. const pick = (col, key) => (r[col] != null && r[col] !== "" ? r[col] : sp[key]); const entryLong = pick("long_entry_logic", "entry_long"); const exitLong = pick("long_exit_logic", "exit_long"); const entryShort = pick("short_entry_logic", "entry_short"); const exitShort = pick("short_exit_logic", "exit_short"); const hasSplit = entryLong || exitLong || entryShort || exitShort; const hasSingle = sp.entry || sp.exit; const riskParams = LOGIC_RISK_KEYS.filter((k) => sp[k] != null).map((k) => `${k}: ${sp[k]}`); const indicators = r.indicators && typeof r.indicators === "object" ? Object.keys(r.indicators).filter((k) => r.indicators[k]) : []; const metaChips = LOGIC_META_KEYS.filter((k) => sp[k] != null); return (
{hasSplit && (
{(entryLong || exitLong) &&
} {(entryShort || exitShort) &&
}
)} {!hasSplit && hasSingle && (
)} {!hasSplit && !hasSingle && (
{riskParams.length > 0 ? "No DSL entry/exit string on file — risk parameters only." : "No entry/exit logic on file for this strategy (pre-DSL era or externally tracked)."}
)} {riskParams.length > 0 && (hasSplit || hasSingle) && (
Risk params: {riskParams.join(" · ")}
)} {riskParams.length > 0 && !hasSplit && !hasSingle && (
{riskParams.join(" · ")}
)} {(indicators.length > 0 || metaChips.length > 0) && (
{indicators.map((k) => {k})} {metaChips.map((k) => {k}: {String(sp[k])})}
)} {sp.note &&
{sp.note}
}
); } // r.baskets comes straight off showcase_basket_members/showcase_baskets — the same tables // set_strategy_status already checks before allowing a retire, so this is the live answer. function BasketsPanel({ r }) { const baskets = r.baskets || []; return ( {baskets.length === 0 &&
Not currently a member of any basket.
} {baskets.length > 0 && (
{baskets.map((b) => (
{b.name} {b.status && } {b.alloc_pct != null && {(Number(b.alloc_pct) * 100).toFixed(1)}%{b.max_leverage ? ` @ ${Number(b.max_leverage)}x` : ""}}
))}
)}
); } // Renders the four decay instruments (p_edge, CUSUM, SPRT, drawdown bands) plus the // regime split, straight from strategy_health.signals -- nothing is recomputed here, // this is just a readable view of numbers the health engine already stored. Also renders // the strategy's entry/exit logic and basket membership, straight off the registry row // (r) — independent of whether health has been computed yet, so those two always show. function HealthBreakdown({ h, r }) { const sig = h.signals || {}; const num = (x, d = 3) => (x == null ? "—" : Number(x).toFixed(d)); const pct = (x, d = 1) => (x == null ? "—" : Number(x).toFixed(d) + "%"); if (!h.computed_at) { return (
Decay not computed yet — either no closed trades, or this strategy hasn't been picked up by the health engine's next run yet.
); } const edgeTone = sig.p_edge == null ? undefined : sig.p_edge < 0.30 ? "var(--red)" : sig.p_edge < 0.45 ? "var(--gold-ink)" : sig.p_edge >= 0.75 ? "var(--green)" : "var(--ink)"; const cusumTone = sig.cusum_alarm ? "var(--red)" : "var(--ink)"; const sprtTone = sig.sprt_verdict === "retire" ? "var(--red)" : sig.sprt_verdict === "keep" ? "var(--green)" : "var(--muted)"; const ddTone = sig.dd_critical ? "var(--red)" : sig.dd_warn ? "var(--gold-ink)" : "var(--ink)"; return (
computed {MonVC.ago(h.computed_at)} · {h.n_live_trades ?? 0} backtest trades feeding this read

); } // ---- Ensemble pilot panel ---------------------------------------------------- function EnsembleGate({ ok, label }) { return (
{ok ? "PASS" : "FAIL"} · {label}
); } function EnsemblePanel({ report, onFile, reportName }) { return (
{false &&

Ensemble pilot (B3)

Phase 3 meta-backtest: run the B3 US-equity ensemble through the Hedge + fixed-share + health-gate logic. Upload the JSON produced by ensemble_meta_backtest.py.

{!report && (
Upload a report JSON to see the ensemble results.
)} {report && (
Ensemble return
{report.ensemble?.total_return_pct ?? "—"}%
Sharpe
{report.ensemble?.sharpe ?? "—"}
MDD
{report.ensemble?.mdd_pct ?? "—"}%
Co-wrong MDD
{report.ensemble?.co_wrong_mdd_pct ?? "—"}%
Turnover / candle
{report.ensemble?.avg_turnover_per_candle ?? "—"}

Go / no-go gates

Benchmarks

{report.benchmarks && Object.entries(report.benchmarks).map(([k, v]) => ( ))}
Variant Return Sharpe MDD
{k.replace(/_/g, " ")} {v.total_return_pct ?? v.simple_return_pct ?? "—"}% {v.sharpe ?? "—"} {v.mdd_pct ?? "—"}%

Parameter grid (top 9)

{report.meta?.grid_candidates?.map((g, i) => ( ))}
η γ Return Sharpe MDD Turnover
{g.eta} {g.gamma} {g.total_return_pct}% {g.sharpe} {g.mdd_pct}% {g.avg_turnover_per_candle}
)}
}
); } // ---- Sortable column header helper ------------------------------------------- function SortHeader({ col, label, sortCol, sortDir, onSort, style }) { const active = sortCol === col; const arrow = active ? (sortDir === "asc" ? " ▲" : " ▼") : ""; return ( onSort(col)} style={{ cursor: "pointer", userSelect: "none", ...style }}> {label}{arrow} ); } // ---- Monitor screen ------------------------------------------------------------ function MonitorScreen({ vault, refresh, openStrategy }) { const [events, setEvents] = React.useState([]); const [loadingEvents, setLoadingEvents] = React.useState(false); const [err, setErr] = React.useState(null); const [filter, setFilter] = React.useState("all"); const [expandedId, setExpandedId] = React.useState(null); const [search, setSearch] = React.useState(""); const [sortCol, setSortCol] = React.useState(null); // null = smart sort const [sortDir, setSortDir] = React.useState("asc"); const [ensembleReport, setEnsembleReport] = React.useState(null); const [ensembleReportName, setEnsembleReportName] = React.useState(""); const [monitorSection, setMonitorSection] = React.useState("health"); React.useEffect(() => { loadEvents(); }, []); function loadEvents() { setLoadingEvents(true); MonVC.api("list_lifecycle_events", { limit: 100 }).then((r) => { setLoadingEvents(false); if (r.status === 200 && r.body.ok) setEvents(r.body.events || []); else setErr("Events: " + (r.body.error || ("HTTP " + r.status))); }).catch((e) => { setLoadingEvents(false); setErr("Events: " + e.message); }); } function onEnsembleFile(e) { const file = e.target.files?.[0]; if (!file) return; setEnsembleReportName(file.name); const reader = new FileReader(); reader.onload = (ev) => { try { const j = JSON.parse(ev.target.result); setEnsembleReport(j); } catch (err) { setErr("Ensemble report parse error: " + err.message); } }; reader.readAsText(file); } const health = vault.health || []; const registry = (vault.registry || []).filter((r) => /^id\d+$/.test(r.id || "")); const healthBySid = {}; health.forEach((h) => { healthBySid[h.strategy_id] = h; }); // SMART SORT — surface what needs a decision, not just the highest score. // Sorting by health_score alone buried every problem: an unscored strategy // (score null -> 0) sorted below a healthy one, and a RETIRE with 40 trades // sat beneath an OK with 2. Rank by how much the row is ASKING FOR ACTION: // 1. a lifecycle recommendation waiting on a human // 2. retire band, then watch — worst first, and only where evidence exists // 3. healthy — best first // 4. not yet scored — nothing to decide, so it goes last // Within each tier, more closed trades wins: a verdict on 40 trades outranks // the same verdict on 3. const BAND_RANK = { retire: 0, watch: 1, ok: 2 }; const actionRank = (r) => { const h = r.h || {}; const scored = h.health_score != null; if (h.lifecycle_recommendation) return 0; // a human is being asked something if (!scored) return 4; // unscored — no decision to make const band = BAND_RANK[h.status]; return band === 0 ? 1 : band === 1 ? 2 : 3; }; function handleSort(col) { if (sortCol === col) { if (sortDir === "asc") setSortDir("desc"); else { setSortCol(null); setSortDir("asc"); } // third click → reset to smart sort } else { setSortCol(col); setSortDir("asc"); } } const rows = registry.map((r) => { const h = healthBySid[r.id] || {}; return { ...r, h }; }).filter((r) => filter === "all" || (r.h?.status || "ok") === filter || (r.h?.lifecycle_status || r.status) === filter || (r.h?.lifecycle_recommendation || "") === filter) .filter((r) => { if (!search) return true; const q = search.toLowerCase(); return (r.id || "").toLowerCase().includes(q) || (r.name || "").toLowerCase().includes(q); }) .sort((a, b) => { if (sortCol) { const dir = sortDir === "asc" ? 1 : -1; switch (sortCol) { case "strategy": return dir * String(a.id).localeCompare(String(b.id), undefined, { numeric: true }); case "score": return dir * ((a.h?.health_score ?? -1) - (b.h?.health_score ?? -1)); case "trades": return dir * ((a.h?.n_live_trades ?? -1) - (b.h?.n_live_trades ?? -1)); case "p_edge": return dir * ((a.h?.signals?.p_edge ?? -1) - (b.h?.signals?.p_edge ?? -1)); default: return 0; } } // default: smart sort (actionRank) const ra = actionRank(a), rb = actionRank(b); if (ra !== rb) return ra - rb; const na = a.h?.n_live_trades || 0, nb = b.h?.n_live_trades || 0; const sa = a.h?.health_score, sb = b.h?.health_score; if (sa != null && sb != null && sa !== sb) return ra <= 2 ? sa - sb : sb - sa; if (na !== nb) return nb - na; return String(a.id).localeCompare(String(b.id), undefined, { numeric: true }); }); const statusCounts = registry.reduce((acc, r) => { const s = r.status || "active"; acc[s] = (acc[s] || 0) + 1; return acc; }, {}); return (
{err && }

Lifecycle overview

{Object.entries(statusCounts).map(([k, v]) => ( ))}
{[['health', 'Strategy Health'], ['decay', 'Decay Review']].map(([key, label]) => ( ))}
{monitorSection === "health" ?

Strategy health

Every registered strategy. Click a row to see the health breakdown.

setSearch(e.target.value)} style={{ padding: "7px 12px", borderRadius: 8, border: "1px solid var(--line)", background: "var(--paper)", color: "var(--ink)", fontFamily: "var(--font-body)", fontSize: 13, width: 220, outline: "none" }} /> {sortCol && }
Health reason Health Updated
{rows.map((r) => { const h = r.h || {}; const sig = h.signals || {}; const rec = h.lifecycle_recommendation; const isOpen = expandedId === r.id; return (
setExpandedId(isOpen ? null : r.id)} style={{ display: "grid", gridTemplateColumns: "1.2fr 1.5fr 80px 76px 80px 100px 100px", gap: 10, padding: "11px 22px", borderBottom: isOpen ? "none" : "1px solid var(--line)", alignItems: "center", fontSize: 13, background: isOpen ? "var(--paper)" : "var(--surface)", cursor: "pointer" }}>
{r.id} {r.asset && r.asset.split("/")[0]}
{r.name}
{h.reason || h.summary || "No health warning"}
{h.health_score ?? "—"}
{h.n_live_trades ?? "—"}
= 0.75 ? "var(--green)" : "var(--ink)" }}>{sig.p_edge != null ? sig.p_edge.toFixed(3) : "—"}
{h.computed_at ? MonVC.ago(h.computed_at) : "—"}
{isOpen && }
); })} {rows.length === 0 &&
No strategies match this filter.
}
: }
{false &&

Lifecycle events

{events.slice(0, 50).map((e) => (
{e.strategy_id} {e.applied ? applied : recommend}
{e.reason}
{MonVC.ago(e.created_at)} · {e.recommended_by}
))} {events.length === 0 &&
No lifecycle events yet.
}
}
); } window.MonitorScreen = MonitorScreen; // Shared with the strategy deep-dive page so the logic on file is visible from the // strategy's own page, not only from Monitor. Referenced there as // window.StrategyLogicPanel because this file loads AFTER screens-strategies.jsx — // the lookup happens at render, by which point every script has parsed. window.StrategyLogicPanel = StrategyLogicPanel;