"use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { Badge, Button, Card, Dialog, Empty, ErrorBox, Field, Input, PageHeader, Select, Spinner, Table, Td, Th } from "@/components/ui"; import { api, ApiError } from "@/lib/api"; import { fmtIso } from "@/lib/format"; import type { AuditItem, RiskLimits, RiskStatus } from "@/lib/types"; const LIMIT_FIELDS: { key: keyof RiskLimits; label: string; hint: string; step?: number }[] = [ { key: "max_order_notional", label: "Max order notional (USD)", hint: "Quote notional per order", step: 100 }, { key: "max_position_notional", label: "Max position notional (USD)", hint: "Per-symbol absolute notional", step: 100 }, { key: "max_gross_exposure", label: "Max gross exposure (×equity)", hint: "Σ|position notional| / equity", step: 0.05 }, { key: "max_net_exposure", label: "Max net exposure (×equity)", hint: "Net long minus short / equity", step: 0.05 }, { key: "max_concentration", label: "Max concentration", hint: "Largest |position| / equity", step: 0.05 }, { key: "daily_loss_limit", label: "Daily loss limit", hint: "Fraction of start-of-day equity", step: 0.005 }, { key: "max_drawdown_limit", label: "Max drawdown limit", hint: "Fraction from rolling peak equity", step: 0.01 }, { key: "min_cash", label: "Minimum cash (USD)", hint: "Must remain after a buy", step: 10 }, { key: "max_open_orders", label: "Max open orders", hint: "Across all symbols", step: 1 }, { key: "max_participation", label: "Max participation", hint: "Order qty / recent bar volume", step: 0.01 }, { key: "max_price_deviation", label: "Max price deviation", hint: "|limit − last| / last", step: 0.005 }, { key: "stale_data_seconds", label: "Stale data cutoff (s)", hint: "Orders are refused when market data is older", step: 5 }, { key: "max_clock_drift_seconds", label: "Max clock drift (s)", hint: "Local vs exchange time", step: 0.5 }, { key: "var_limit_95", label: "VaR 95% limit (optional)", hint: "1-bar VaR as fraction of equity; blank = off", step: 0.005 }, ]; export default function RiskPage() { const qc = useQueryClient(); const status = useQuery({ queryKey: ["risk-status"], queryFn: () => api("/api/risk/status"), refetchInterval: 10_000 }); const audit = useQuery({ queryKey: ["audit"], queryFn: () => api<{ items: AuditItem[]; chain_valid: boolean; chain_length: number }>("/api/audit?limit=100"), refetchInterval: 15_000 }); const [formState, setForm] = useState(null); const form = formState ?? status.data?.limits ?? null; const save = useMutation({ mutationFn: (b: RiskLimits) => api("/api/risk/limits", { method: "PUT", json: b }), onSuccess: () => { qc.invalidateQueries({ queryKey: ["risk-status"] }); qc.invalidateQueries({ queryKey: ["audit"] }); } }); const [ksOpen, setKsOpen] = useState(false); const [ksReason, setKsReason] = useState(""); const kill = useMutation({ mutationFn: (active: boolean) => api("/api/risk/kill-switch", { method: "POST", json: { active, reason: ksReason || null } }), onSuccess: () => { setKsOpen(false); setKsReason(""); qc.invalidateQueries({ queryKey: ["risk-status"] }); qc.invalidateQueries({ queryKey: ["audit"] }); } }); const ksActive = status.data?.kill_switch.active ?? false; // what-if evaluator const [wi, setWi] = useState({ symbol: "BTC/USD", side: "buy", qty: 0.01, order_type: "market", limit_price: "", equity: 10000, cash: 10000, position: 0, last_price: 80000, age: 1, recent_vol: "", ordermin: 0.0001, costmin: 0.5 }); const evalQ = useMutation({ mutationFn: () => api<{ allowed: boolean; reasons: string[]; checks: Record }>("/api/risk/evaluate", { method: "POST", json: { intent: { symbol: wi.symbol, side: wi.side, qty: Number(wi.qty), order_type: wi.order_type, limit_price: wi.limit_price === "" ? null : Number(wi.limit_price) }, portfolio: { equity: Number(wi.equity), cash: Number(wi.cash), positions: Number(wi.position) ? { [wi.symbol]: Number(wi.position) } : {}, marks: { [wi.symbol]: Number(wi.last_price) }, open_orders: 0, day_start_equity: Number(wi.equity), peak_equity: Number(wi.equity), reconciled: true }, market: { last_price: Number(wi.last_price), data_age_seconds: Number(wi.age), recent_bar_volume: wi.recent_vol === "" ? null : Number(wi.recent_vol), ordermin: Number(wi.ordermin), costmin: Number(wi.costmin) }, } }), }); return ( <> KILL SWITCH ACTIVE : kill switch armed)} />
{status.isLoading && } {status.data && (
Status: {ksActive ? ACTIVE : armed (inactive)}
{status.data.kill_switch.reason &&
Reason: {status.data.kill_switch.reason}
} {status.data.kill_switch.since &&
Since {fmtIso(new Date(status.data.kill_switch.since * 1000).toISOString())}
}
)} setKsOpen(false)} title={ksActive ? "Disarm kill switch?" : "Activate kill switch?"} footer={<>}>

{ksActive ? "New orders will be allowed again, subject to all other limits." : "All new orders from every engine will be rejected until disarmed. This is recorded in the immutable audit log."}

setKsReason(e.target.value)} placeholder="e.g. anomalous prices on BTC/USD" /> {kill.error && }
form && save.mutate(form)}>Save limits}> {form ? (
{ e.preventDefault(); save.mutate(form); }} className="grid grid-cols-2 gap-3 md:grid-cols-3 xl:grid-cols-4"> {LIMIT_FIELDS.map((f) => ( setForm({ ...form, [f.key]: e.target.value === "" ? (f.key === "var_limit_95" ? null : 0) : Number(e.target.value) })} /> ))} {save.error &&
} {save.isSuccess &&

Limits saved and audited.

}
) : }
{ e.preventDefault(); evalQ.mutate(); }} className="grid grid-cols-2 gap-3 md:grid-cols-4"> setWi({ ...wi, symbol: e.target.value })} /> setWi({ ...wi, qty: Number(e.target.value) })} /> setWi({ ...wi, limit_price: e.target.value })} placeholder="market" /> setWi({ ...wi, last_price: Number(e.target.value) })} /> setWi({ ...wi, equity: Number(e.target.value) })} /> setWi({ ...wi, cash: Number(e.target.value) })} /> setWi({ ...wi, position: Number(e.target.value) })} /> setWi({ ...wi, age: Number(e.target.value) })} /> setWi({ ...wi, recent_vol: e.target.value })} placeholder="unknown" />
setWi({ ...wi, ordermin: Number(e.target.value) })} /> setWi({ ...wi, costmin: Number(e.target.value) })} />
{evalQ.error && } {evalQ.data && (
{evalQ.data.allowed ? ALLOWED : REJECTED}{evalQ.data.reasons.length > 0 && {evalQ.data.reasons.join("; ")}}
    {Object.entries(evalQ.data.checks).map(([k, ok]) =>
  • {ok ? "✓" : "✗"} {k}
  • )}
)}
{status.data?.recent_events.length ? ( {status.data.recent_events.map((e, i) => )}
TimeActionDetails
{fmtIso(e.ts)}{e.action}{JSON.stringify(e.details)}
) : }
chain {audit.data.chain_valid ? "valid" : "BROKEN"} · {audit.data.chain_length} entries}> {audit.isLoading && } {audit.data && (audit.data.items.length ? ( {audit.data.items.map((a) => )}
#TimeActorActionEntityDetailsHash
{a.id}{fmtIso(a.ts)}{a.actor}{a.action}{a.entity ?? ""}{JSON.stringify(a.details)}{a.hash.slice(0, 12)}
) : )}
); }