/* 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)."}
);
}
// 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 &&
)}
);
}
// 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.
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.
);
}
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;