/* global React */ /* Vault Control — Trade Log + Ops (open positions, live positions, orders, alerts). */ const { Card: TlCard } = window.BraveAlphaCapitalDesignSystem_c4b179; const { FMT: TlF, toneOf: tlTone, SourceTag: TlSrc, InfoDot: TlInfo, depsBySid: tlDepsBySid, venueOf: tlVenueOf } = window; const { SearchSelect: TlSearchSelect } = window; const TL = window.VC; function tlReg(vault, sid) { return (vault.registry || []).find((r) => r.id === sid) || {}; } // ---- Open positions — every strategy (moved here from Strategies, 2026-07-21) -------- // One open row per strategy with an open trade, priced from the latest candle. Vocabulary // matches SourceTag everywhere: LIVE = armed real money; PAPER = the evaluator's paper track. function OpenTradesTable({ vault, openStrategy, bare }) { const depMapAll = tlDepsBySid(vault.deploymentsAll || vault.deployments || []); const isSimAcct = (a) => (tlVenueOf ? tlVenueOf(a) : a) === "PAPER" || a === "paper_main" || a === "hyperliquid_testnet"; const basketOf = {}; (vault.builderBaskets || []).filter((b) => b.status !== "archived") .forEach((b) => (b.members || []).forEach((m) => { basketOf[m.strategy_id] = b.name; })); // retired strategies keep their ledger but their dangling opens are noise, not positions const open = (vault.openTrades || []).filter((t) => t.registry_status !== "retire"); if (!open.length) return bare ?
no open positions
: null; const bySym = {}; open.forEach((t) => { const s = t.symbol || (tlReg(vault, t.strategy_id).symbol || "?"); (bySym[s] = bySym[s] || []).push(t); }); const syms = Object.keys(bySym).sort(); const chipOf = (sid) => { const d = depMapAll[sid]; const liveReal = d && (d.enabled === true || d.mode === "live") && !isSimAcct(d.account_id); if (liveReal) return { t: "LIVE", c: "var(--green)", tip: "Armed — real money on " + (tlVenueOf ? tlVenueOf(d.account_id) : d.account_id) }; if (basketOf[sid]) return { t: "PAPER", c: "#3d7ea6", tip: "In basket “" + basketOf[sid] + "” — not armed; this is the evaluator's paper track" }; return { t: "PAPER", c: "#3d7ea6", tip: "Registry strategy — evaluator paper track only" }; }; const pctTone = (v) => (v == null ? "var(--muted)" : tlTone(v)); const gridOP = "minmax(170px,1.4fr) 74px 110px 110px 90px 100px 110px"; const Wrap = bare ? "div" : TlCard; return ( {!bare && (

Open positions — every strategy

Every open position across all strategies, grouped by asset and priced from the latest candle. Only LIVE rows are real money — PAPER is the evaluator running the same signals without money.

)}
StrategySide Entry Now Open P&L OpenedMoney
{syms.map((sym) => { const rows = bySym[sym].slice().sort((a, b) => (chipOf(a.strategy_id).t === "LIVE" ? -1 : 0) - (chipOf(b.strategy_id).t === "LIVE" ? -1 : 0) || (b.unrealized_pnl_pct || 0) - (a.unrealized_pnl_pct || 0)); const px = rows.find((r) => r.current_price != null); const pxTs = px && px.price_ts ? TL.ago(new Date(px.price_ts * 1000).toISOString()) : null; return (
{sym} {rows.length} open{px ? " · " + Number(px.current_price).toLocaleString() : ""}{pxTs ? " · priced " + pxTs : ""}
{rows.map((t) => { const reg = tlReg(vault, t.strategy_id); const chip = chipOf(t.strategy_id); const sideCol = t.type === "long" ? "var(--green)" : "var(--red)"; return (
{String(t.type || "").toUpperCase()} {t.entry_price != null ? Number(t.entry_price).toLocaleString() : "—"} {t.current_price != null ? Number(t.current_price).toLocaleString() : "—"} {t.unrealized_pnl_pct != null ? TlF.pct(t.unrealized_pnl_pct, 2) : "—"} {t.entry_time ? TL.ago(t.entry_time) : "—"} {chip.t}
); })}
); })}
); } // ---- Positions: two tabs, Live (real broker positions, every account) first ---- function PositionsTabs({ vault, refresh, openStrategy }) { const [tab, setTab] = React.useState("live"); return (
{[["live", "Live positions"], ["open", "Open positions — every strategy"]].map(([id, lb]) => ( ))} {tab === "live" ? "broker positions · every connected account" : "every strategy's open trade · LIVE = money, PAPER = evaluator"}
{tab === "live" ?
: }
); } function SideTag({ side }) { const s = String(side || "").toLowerCase(); const long = s === "long" || s === "buy"; return {s.toUpperCase()}; } // ---- manual close of a live position (Control → Supabase → GCP reconciler) ------- // Two-step confirm. Calls close_trade: books the exit + writes a manual_closes suppression // marker so the reconciler flattens the broker position and the evaluator won't re-open it. function CloseCell({ sid, orphan, refresh }) { const [st, setSt] = React.useState(null); if (!sid) return ; if (st === "done") return closing…; if (st === "busy") return ; if (st && st.err) return error ⚠; function go() { setSt("busy"); window.VC.api("close_trade", { strategy_id: sid, note: "manual close from Trade Log" }).then((r) => { if (r.status === 200 && r.body.ok) { setSt("done"); setTimeout(() => refresh && refresh(), 1400); } else setSt({ err: (r.body && r.body.error) || ("HTTP " + r.status) }); }).catch((e) => setSt({ err: e.message })); } if (st === "confirm") return ( ); // Orphan = the position outlived its deployment (disarmed while still open). Same // action, but flagged gold so the operator knows nothing is managing this line and // it will sit there until someone closes it. const tone = orphan ? "var(--gold)" : "var(--red)"; return ; } // ---- Ops: live positions ----------------------------------------------------------- // Recent orders used to sit beside this in a 2-col grid; it now renders full-width lower down // (see RecentOrders) — it needs the width for its filters, and it is reference, not cockpit. function OpsTables({ vault, refresh }) { // Collapse duplicate rows for the same economic position. A HIP-3 builder-dex position // carries a dex prefix ("xyz:AMD") while a pre-switch bare row ("AMD") can linger in the // live_positions cache until the position closes (reconcile only purges the legacy bare // twin on close). Key by (account, base symbol), keep the freshest row, so the cockpit // shows one live line — never a stale duplicate. const canonSym = (s) => String(s || "").replace(/^[a-z0-9]+:/i, "").toUpperCase(); const tsOf = (s) => Date.parse(String(s || "").replace(" ", "T")) || 0; const posByKey = {}; (vault.positions || []).forEach((p) => { const k = String(p.account_id || TL.VAULT) + "|" + canonSym(p.symbol); if (!posByKey[k] || tsOf(p.updated_at) > tsOf(posByKey[k].updated_at)) posByKey[k] = p; }); const pos = Object.values(posByKey).sort((a, b) => (String(a.account_id) + "|" + canonSym(a.symbol)).localeCompare(String(b.account_id) + "|" + canonSym(b.symbol))); // (account, base symbol) -> owning strategy, so a position's Close writes THAT account's // strategy — symbol alone is ambiguous once positions span every broker account, and the // deployment symbol is bare ("AMD") while the exchange position may be prefixed ("xyz:AMD"). // // This used to key on `d.enabled` ALONE, which silently hid Close on any position whose // strategy had been disarmed. Disarm (apply_change) only flips enabled/mode — it never // flattens — so a live position routinely outlives its deployment, and the one control // that could close it disappeared exactly when it was needed (SUI/hyperliquid_vault, // 2026-09-07: -$52 short, both SUI deployments in shadow, no Close button, no owner). // Rank instead: an armed deployment wins, then one that actually holds the open trade, // so a disarmed-but-open sleeve still gets a (gold) Close. const openSids = {}; (vault.openTrades || []).forEach((t) => { openSids[t.strategy_id] = true; }); const depRank = (d) => (d.enabled ? 2 : 0) + (openSids[d.strategy_id] ? 1 : 0); const depByKey = {}; (vault.deploymentsAll || vault.deployments || []).forEach((d) => { const k = String(d.account_id || TL.VAULT) + "|" + canonSym(d.symbol); if (!depByKey[k] || depRank(d) > depRank(depByKey[k])) depByKey[k] = d; }); const th = { fontSize: 11, fontWeight: 600, letterSpacing: ".05em", textTransform: "uppercase", color: "var(--muted)" }; const grid = "68px 84px 60px 1fr 1fr 1fr 82px 44px 70px 76px"; return (
SymbolAccountSideQtyEntryMarkuPnLLevUpdatedClose
{pos.map((p, i) => { const up = Number(p.unrealized_pnl); const dep = depByKey[String(p.account_id || TL.VAULT) + "|" + canonSym(p.symbol)]; return (
{canonSym(p.symbol)} {tlVenueOf ? tlVenueOf(p.account_id || TL.VAULT) : (p.account_id || "vault")} {TlF.num(Number(p.qty), 4)} {TlF.num(Number(p.entry_price))} {TlF.num(Number(p.mark_price))} {(up >= 0 ? "+" : "") + TlF.num(up)} {TlF.num(Number(p.leverage), 1)}× {TL.ago(p.updated_at)}
); })} {pos.length === 0 &&
no open positions
}
); } // ---- Recent orders: club the error spam, filter, paginate -------------------------- const ORD_PAGE = 10; // One signature per error FAMILY, so N identical transport failures collapse to one row. // `reason` is already on the wire (list_vault selects it on execution_orders) — it was simply // never rendered, which is why a screen of errors read as anonymous "error" ×11. Strip the // volatile "reconcile->FLAT: " prefix and digits so one root cause clubs into one row. function ordSig(o) { return String(o.status || "") + "|" + String(o.reason || "") .replace(/^[^:]*:\s*/, "").replace(/\d+/g, "#").slice(0, 80); } function ordKey(o, i) { return String(o.created_at || "") + "|" + String(o.strategy_id || "") + "|" + String(o.symbol || "") + "|" + i; } function RecentOrders({ vault }) { const ords = vault.orders || []; const [statusF, setStatusF] = React.useState("all"); const [symF, setSymF] = React.useState("all"); const [acctF, setAcctF] = React.useState("all"); const [page, setPage] = React.useState(0); const th = { fontSize: 11, fontWeight: 600, letterSpacing: ".05em", textTransform: "uppercase", color: "var(--muted)" }; const syms = Array.from(new Set(ords.map((o) => String(o.symbol || "").toUpperCase()).filter(Boolean))).sort(); const accts = Array.from(new Set(ords.map((o) => String(o.account_id || "")).filter(Boolean))).sort(); const nErr = ords.filter((o) => o.status === "error").length; // dry_run is perfectly collinear with status='intended' on real data, so it is NOT a separate // filter axis — "Live attempts" is the same cut as "not intended", just named for the operator. const rows = React.useMemo(() => { let ts = ords.slice(); if (statusF === "errors") ts = ts.filter((o) => o.status === "error"); else if (statusF === "filled") ts = ts.filter((o) => o.status === "filled"); else if (statusF === "live") ts = ts.filter((o) => !o.dry_run); else if (statusF === "dry") ts = ts.filter((o) => !!o.dry_run); if (symF !== "all") ts = ts.filter((o) => String(o.symbol || "").toUpperCase() === symF); if (acctF !== "all") ts = ts.filter((o) => String(o.account_id || "") === acctF); const out = [], seen = {}; ts.forEach((o, i) => { if (o.status !== "error") { out.push({ o, n: 1, k: ordKey(o, i) }); return; } const s = ordSig(o); if (seen[s]) { seen[s].n += 1; return; } // club: newest row wins, count the rest seen[s] = { o, n: 1, k: ordKey(o, i) }; out.push(seen[s]); }); return out; }, [ords, statusF, symF, acctF]); const pages = Math.max(1, Math.ceil(rows.length / ORD_PAGE)); const pg = Math.min(page, pages - 1); const view = rows.slice(pg * ORD_PAGE, pg * ORD_PAGE + ORD_PAGE); const sel = { fontFamily: "var(--font-body)", fontSize: 13, padding: "6px 10px", borderRadius: 8, border: "1px solid var(--line)", background: "var(--surface)", color: "var(--ink)", cursor: "pointer" }; const grid = "72px 70px 66px 82px 66px 90px 90px 96px 1fr"; return (

Recent orders

execution_orders · every account · newest {ords.length}{nErr ? " · " + nErr + " error" + (nErr === 1 ? "" : "s") + " clubbed by cause" : ""}
TimeStrategySymbolAccountSideQtyFillStatusReason
{view.map((row, i) => { const o = row.o; const err = o.status === "error"; return (
{TL.ago(o.created_at)} {o.strategy_id} {o.symbol} {o.account_id ? (tlVenueOf ? tlVenueOf(o.account_id) : o.account_id) : "—"} {TlF.num(Number(o.qty), 4)} {TlF.num(Number(o.avg_fill_price != null ? o.avg_fill_price : o.price))} {o.status || "—"} {row.n > 1 && ×{row.n}} {o.dry_run && DRY} {o.reason || "—"}
); })} {view.length === 0 &&
no orders match these filters
}
{pages > 1 && (
Page {pg + 1} / {pages} {rows.length} row{rows.length === 1 ? "" : "s"} after clubbing
)}
); } // ---- Alerts & halts ------------------------------------------------------------------ function AlertsList({ vault }) { const items = []; (vault.halts || []).forEach((h) => items.push({ sev: "critical", who: h.scope, msg: "[HALT · " + h.event_type + "] " + (h.reason || ""), at: h.created_at })); (vault.alerts || []).forEach((a) => items.push({ sev: a.severity === "warn" ? "warning" : (a.severity || "info"), who: a.strategy_id, msg: "[" + a.alert_type + "] " + (a.message || ""), at: a.created_at })); return (

Alerts & halts

{items.map((a, i) => { const col = a.sev === "warning" ? "var(--gold-ink)" : a.sev === "critical" ? "var(--red)" : "var(--muted)"; return (
{a.sev} {a.who || ""} {a.msg} {TL.ago(a.at)}
); })} {items.length === 0 &&
none
}
); } // ---- Closed-trade log ------------------------------------------------------------------- function TradeTable({ vault, mode, openStrategy }) { // "Deployed" = strategies holding a live_deployments row on ANY account (they also appear // under "Every strategy" — deployed is a subset, not a separate universe). const deployedSids = Array.from(new Set((vault.deploymentsAll || vault.deployments || []).map((d) => d.strategy_id))); const regs = (vault.registry || []).filter((r) => /^id\d+$/.test(r.id || "")); const strategyOptions = [ { value: "deployed", label: "Deployed only", keywords: "live execution armed" }, { value: "all", label: "Every strategy (including deployed)", keywords: "all registry" }, ...regs.map((r) => ({ value: r.id, label: `${r.id} · ${r.name || r.symbol}`, keywords: `${r.symbol || ""} ${r.asset || ""} ${r.timeframe || ""} ${r.direction || ""} ${r.status || ""}` })), ]; const [stratF, setStratF] = React.useState("deployed"); const [srcF, setSrcF] = React.useState("real"); // default to LIVE FILLS — real money first; widen deliberately const [typeF, setTypeF] = React.useState("all"); const [resF, setResF] = React.useState("all"); const [from, setFrom] = React.useState(""); const [to, setTo] = React.useState(""); const [loading, setLoading] = React.useState(false); const [tick, setTick] = React.useState(0); function pickStrat(v) { setStratF(v); } React.useEffect(() => { const sids = stratF === "deployed" ? deployedSids : stratF === "all" ? regs.map((r) => r.id) : [stratF]; const jobs = []; if (srcF === "all" || srcF === "backtest") jobs.push(TL.ensureRegistryBacktesting(sids)); if (srcF === "all" || srcF === "paper" || srcF === "real") jobs.push(TL.ensureTrades(sids)); if (!jobs.length) return; setLoading(true); Promise.all(jobs).then(() => { setLoading(false); setTick((t) => t + 1); }).catch(() => setLoading(false)); }, [stratF, srcF, vault]); const rows = React.useMemo(() => { const sids = stratF === "deployed" ? deployedSids : stratF === "all" ? regs.map((r) => r.id) : [stratF]; let ts = TL.allTrades(sids).filter((t) => !t.open); if (srcF !== "all") ts = ts.filter((t) => srcF === "paper" ? t.source === "live" : srcF === "real" ? t.source === "live_real" : t.source === "backtest"); if (typeF !== "all") ts = ts.filter((t) => t.type === typeF); if (resF !== "all") ts = ts.filter((t) => resF === "win" ? t.pnl > 0 : t.pnl <= 0); if (from) ts = ts.filter((t) => t.exit >= from); if (to) ts = ts.filter((t) => t.exit <= to + " 23:59"); return ts.slice().sort((a, b) => TL.parseT(b.exit) - TL.parseT(a.exit)); }, [vault, srcF, stratF, typeF, resF, from, to, tick]); const wins = rows.filter((t) => t.pnl > 0).length; // Aggregate P&L. Chaining every row through ∏(1+pnl) treats the whole book as one pot of // capital reinvested trade after trade — but strategies are PARALLEL sleeves, each sized at // its own alloc_pct of the same equity. Compounding across them multiplied the default // 8-strategy view up to ~+5700% against an allocation-weighted ~+85%. Compound WITHIN a // strategy (that sleeve really is sequential), then weight across strategies by allocation. const totals = React.useMemo(() => { const bySid = {}; rows.forEach((t) => { (bySid[t.sid] = bySid[t.sid] || []).push(t); }); const sids = Object.keys(bySid); const retOf = (ts) => ts.reduce((acc, t) => acc * (1 + t.pnl / 100), 1) * 100 - 100; if (!sids.length) return { v: 0, label: "Compounded P&L" }; if (sids.length === 1) return { v: retOf(bySid[sids[0]]), label: "Compounded P&L" }; const depOf = {}; (vault.deploymentsAll || vault.deployments || []).forEach((d) => { depOf[d.strategy_id] = d; }); let acc = 0, wsum = 0; sids.forEach((s) => { const w = Number((depOf[s] || {}).alloc_pct || 0); if (w > 0) { acc += w * retOf(bySid[s]); wsum += w; } }); // no allocations at all (a registry-only selection) — say it's a plain average, don't imply a book if (wsum <= 0) return { v: sids.reduce((s, k) => s + retOf(bySid[k]), 0) / sids.length, label: "Mean return / strategy" }; return { v: acc / wsum, label: "Alloc-weighted P&L" }; }, [rows, vault]); const totalPnl = totals.v; const sel = { fontFamily: "var(--font-body)", fontSize: 13, padding: "8px 11px", borderRadius: 8, border: "1px solid var(--line)", background: "var(--surface)", color: "var(--ink)", cursor: "pointer" }; const dateI = { fontFamily: "var(--font-mono)", fontSize: 13, padding: "8px 11px", borderRadius: 8, border: "1px solid var(--line)", background: "var(--surface)", color: "var(--ink)" }; const grid = "minmax(150px,1.2fr) 70px 140px 140px 70px 100px 80px"; return (
Filters setFrom(e.target.value)} style={dateI} /> setTo(e.target.value)} style={dateI} /> {loading ? "loading ledger…" : rows.length + " trades"}
StrategySourceEntryExitSideP&L %Held
{rows.map((t, i) => { const r = tlReg(vault, t.sid); const held = (TL.parseT(t.exit) - TL.parseT(t.entry)) / 3.6e6; return (
{t.entry} {t.exit} {t.type} {TlF.pct(t.pnl, 2)} {isFinite(held) ? TlF.dur(held) : "—"}
); })} {rows.length === 0 &&
No trades match these filters.
}
{rows.length} trades {wins}W / {rows.length - wins}L {totals.label}: {TlF.pct(totalPnl)}
); } function TradeLog({ vault, mode, openStrategy, refresh }) { return (
); } Object.assign(window, { TradeLog, PositionsTabs, OpenTradesTable, OpsTables, RecentOrders, AlertsList });