All checks were successful
deploy / deploy (push) Successful in 1m3s
Proof: Strategy tests now cover USDC -> BTC maker responses using BTC inventory with zero USDC, pending outbound units are subtracted before approval, lifecycle rows expose maker send/receive terms and inventory check details, targeted dashboard tests pass, full npm test passes, and the operator dashboard bundle builds. Assumptions: pending_outbound in inventory snapshots represents units unavailable for new maker commitments; this change does not skip quotes because of relay-error risk and does not loosen edge, notional, arming, pair enablement, stale price, or stale inventory checks. Still fake: relay acceptance is still only submission evidence; venue-native terminal fill ids and fee-complete realized PnL remain unavailable.
1442 lines
62 KiB
JavaScript
1442 lines
62 KiB
JavaScript
import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
import EmptyState from '../components/EmptyState.jsx';
|
|
import MetricCard from '../components/MetricCard.jsx';
|
|
import Pill from '../components/Pill.jsx';
|
|
import TableFrame from '../components/TableFrame.jsx';
|
|
import { formatAgeFromTimestamp, formatBoolean, formatEur, formatTimestamp, truncateMiddle } from '../lib/format.js';
|
|
|
|
const RESPONDED_STATES = new Set(['submitted', 'awaiting_outcome', 'not_filled', 'completed']);
|
|
const TRADING_PAIR_MODES = new Set(['maker', 'taker', 'both']);
|
|
const COMPETITIVENESS_GROUP_ROW_COUNT = 12;
|
|
const COMPETITIVENESS_DETAIL_ROW_COUNT = 8;
|
|
const COMPETITIVENESS_LATENCY_ROW_COUNT = 6;
|
|
const QUOTE_LIFECYCLE_ROW_COUNT = 20;
|
|
|
|
async function copyIdentifier(value) {
|
|
if (!value || !navigator?.clipboard?.writeText) return;
|
|
try {
|
|
await navigator.clipboard.writeText(value);
|
|
} catch {
|
|
// Best-effort copy affordance; keep the full identifier visible regardless.
|
|
}
|
|
}
|
|
|
|
function useNow(intervalMs = 1000) {
|
|
const [now, setNow] = useState(() => Date.now());
|
|
|
|
useEffect(() => {
|
|
const timer = window.setInterval(() => setNow(Date.now()), intervalMs);
|
|
return () => window.clearInterval(timer);
|
|
}, [intervalMs]);
|
|
|
|
return now;
|
|
}
|
|
|
|
function formatRelativeAge(value, now) {
|
|
const age = formatAgeFromTimestamp(value, now);
|
|
return age === 'Unavailable' ? 'Age unavailable' : `${age} ago`;
|
|
}
|
|
|
|
function formatTimingMs(value) {
|
|
const number = Number(value);
|
|
if (!Number.isFinite(number)) return null;
|
|
return `${number < 10 ? number.toFixed(1) : number.toFixed(0)} ms`;
|
|
}
|
|
|
|
function formatRate(value) {
|
|
const number = Number(value);
|
|
if (!Number.isFinite(number)) return 'Unavailable';
|
|
return `${(number * 100).toFixed(1)}%`;
|
|
}
|
|
|
|
function stageLabel(value) {
|
|
return plainCodeLabel(value).replace(/\bms\b/i, '').trim();
|
|
}
|
|
|
|
function formatExecutionTiming(timing) {
|
|
if (!timing) return null;
|
|
const saltMs = formatTimingMs(timing.current_salt_ms);
|
|
const saltAgeMs = formatTimingMs(timing.current_salt_age_ms);
|
|
const saltSource = timing.current_salt_source ? ` ${plainCodeLabel(timing.current_salt_source).toLowerCase()}` : '';
|
|
const parts = [
|
|
['total', timing.executor_total_ms],
|
|
['cmd age', timing.command_event_age_ms],
|
|
['sign', timing.sign_ms],
|
|
['relay', timing.relay_response_ms],
|
|
]
|
|
.map(([label, value]) => {
|
|
const formatted = formatTimingMs(value);
|
|
return formatted ? `${label} ${formatted}` : null;
|
|
})
|
|
.filter(Boolean);
|
|
if (saltMs) {
|
|
parts.splice(2, 0, `salt ${saltMs}${saltSource}${saltAgeMs ? ` age ${saltAgeMs}` : ''}`);
|
|
}
|
|
return parts.length ? `Timing: ${parts.join(', ')}` : null;
|
|
}
|
|
|
|
function IdentifierRow({ label, value }) {
|
|
if (!value) return <div className="status-subtle">{`${label}: unavailable`}</div>;
|
|
|
|
return (
|
|
<div className="trace-row">
|
|
<span className="status-subtle">{`${label}:`}</span>
|
|
<span className="mono trace-id">{value}</span>
|
|
<button className="button secondary trace-copy-button" onClick={() => copyIdentifier(value)} type="button">
|
|
Copy
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function formatTerms(terms) {
|
|
if (!terms) return 'Unavailable';
|
|
const input = terms.amount_in
|
|
? `${terms.amount_in} ${terms.asset_in_symbol || ''}`.trim()
|
|
: terms.asset_in_symbol || terms.asset_in || 'input unavailable';
|
|
const output = terms.amount_out
|
|
? `${terms.amount_out} ${terms.asset_out_symbol || ''}`.trim()
|
|
: terms.asset_out_symbol || terms.asset_out || 'output unavailable';
|
|
return `${input} -> ${output}`;
|
|
}
|
|
|
|
function formatMakerTerms(terms) {
|
|
if (!terms) return null;
|
|
const send = terms.send_amount
|
|
? `${terms.send_amount} ${terms.send_asset_symbol || ''}`.trim()
|
|
: terms.send_asset_symbol || terms.send_asset || 'send unavailable';
|
|
const receive = terms.receive_amount
|
|
? `${terms.receive_amount} ${terms.receive_asset_symbol || ''}`.trim()
|
|
: terms.receive_asset_symbol || terms.receive_asset || 'receive unavailable';
|
|
return `Maker sends ${send}; receives ${receive}`;
|
|
}
|
|
|
|
function formatInventoryCheck(check) {
|
|
if (!check) return null;
|
|
const required = check.required == null ? 'required unavailable' : `${check.required} ${check.asset_symbol || ''}`.trim();
|
|
const available = check.available == null ? 'available unavailable' : `${check.available} available`;
|
|
const spendable = check.spendable == null ? null : `${check.spendable} spendable`;
|
|
const pendingOutbound = Number(check.pending_outbound_units || 0) > 0
|
|
? `${check.pending_outbound} pending outbound`
|
|
: null;
|
|
return [`Inventory ${required}`, available, spendable, pendingOutbound].filter(Boolean).join(', ');
|
|
}
|
|
|
|
function responseLabel(item) {
|
|
if (RESPONDED_STATES.has(item.lifecycle_state)) return 'Yes';
|
|
if (item.lifecycle_state === 'failed') return 'Attempt failed';
|
|
if (item.lifecycle_state === 'blocked' && item.reason_code?.startsWith('maker_')) return 'No - policy skip';
|
|
if (item.lifecycle_state === 'blocked') return 'No - blocked';
|
|
if (item.lifecycle_state === 'rejected') return 'No - strategy rejected';
|
|
if (item.lifecycle_state === 'command_emitted') return 'Pending executor';
|
|
if (item.lifecycle_state === 'evaluated') return 'Approved, not sent';
|
|
return 'No decision yet';
|
|
}
|
|
|
|
function grossEdgeEstimate(item) {
|
|
if (!item.gross_edge_value) return 'Unavailable';
|
|
const symbol = item.notional_symbol || (item.eure_notional ? 'EURe' : null);
|
|
if (symbol === 'EURe') return formatEur(item.gross_edge_value);
|
|
return symbol ? `${item.gross_edge_value} ${symbol}` : item.gross_edge_value;
|
|
}
|
|
|
|
function formatGrossEdgePct(value) {
|
|
if (value == null || value === '') return 'Gross edge unavailable';
|
|
return `Gross edge ${value}%`;
|
|
}
|
|
|
|
function formatConfiguredEdgeBps(value, { prefix = true } = {}) {
|
|
const number = Number(value);
|
|
if (!Number.isFinite(number)) return prefix ? 'Configured edge unavailable' : 'Unavailable';
|
|
const label = `${value} bps (${(number / 100).toFixed(2)}%)`;
|
|
return prefix ? `Configured edge ${label}` : label;
|
|
}
|
|
|
|
function notionalLabel(item) {
|
|
if (item.notional_display) return item.notional_display;
|
|
if (item.notional != null) return `${item.notional}${item.notional_symbol ? ` ${item.notional_symbol}` : ''}`;
|
|
return item.eure_notional ? formatEur(item.eure_notional) : 'Notional unavailable';
|
|
}
|
|
|
|
function plainCodeLabel(value, fallback = 'Unavailable') {
|
|
const text = String(value || '').trim();
|
|
if (!text) return fallback;
|
|
return text.replaceAll('_', ' ');
|
|
}
|
|
|
|
function strategyDecisionStatus(decision) {
|
|
if (decision?.decision === 'approved') return 'Strategy approved';
|
|
if (decision?.decision === 'rejected') return 'Strategy rejected';
|
|
return plainCodeLabel(decision?.decision, 'No strategy decision');
|
|
}
|
|
|
|
function isStrategyRejected(item) {
|
|
return item?.lifecycle_state === 'rejected'
|
|
|| item?.decision?.decision === 'rejected'
|
|
|| String(item?.lifecycle_label || '').toLowerCase() === 'rejected by strategy';
|
|
}
|
|
|
|
function StageCard({ title, at, status, children }) {
|
|
return (
|
|
<div className="lifecycle-stage-card">
|
|
<div className="stage-title">{title}</div>
|
|
<div className="stage-meta">{formatTimestamp(at)}</div>
|
|
<div className="stage-status">{status || 'Not recorded'}</div>
|
|
{children ? <div className="stage-body">{children}</div> : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TimingWaterfall({ timing }) {
|
|
if (!timing) return null;
|
|
const rows = [
|
|
['Quote observed', timing.quote_observed_at],
|
|
['Quote received', timing.quote_received_at],
|
|
['Normalized', timing.quote_normalized_at],
|
|
['Published', timing.quote_published_at],
|
|
['Strategy received', timing.strategy_received_at],
|
|
['Strategy decided', timing.strategy_decided_at, timing.quote_to_decision_ms],
|
|
['Command published', timing.command_published_at, timing.decision_to_command_ms],
|
|
['Executor received', timing.executor_received_at, timing.command_to_executor_ms],
|
|
['Relay result', timing.relay_result_at, timing.executor_to_relay_result_ms],
|
|
['Outcome observed', timing.outcome_observed_at, timing.quote_to_outcome_ms],
|
|
];
|
|
const hasEvidence = rows.some(([, at, duration]) => at || duration != null);
|
|
if (!hasEvidence) return null;
|
|
|
|
return (
|
|
<TableFrame>
|
|
<table className="timing-waterfall-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Stage</th>
|
|
<th>Timestamp</th>
|
|
<th>Step</th>
|
|
<th>From quote</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows.map(([label, at, duration]) => (
|
|
<tr key={label}>
|
|
<td>{label}</td>
|
|
<td>{formatTimestamp(at)}</td>
|
|
<td>{formatTimingMs(duration) || 'Unavailable'}</td>
|
|
<td>
|
|
{label === 'Strategy decided' ? formatTimingMs(timing.quote_age_at_decision_ms)
|
|
: label === 'Executor received' ? formatTimingMs(timing.quote_age_at_executor_receipt_ms)
|
|
: label === 'Relay result' ? formatTimingMs(timing.quote_age_at_relay_result_ms)
|
|
: label === 'Outcome observed' ? formatTimingMs(timing.quote_to_outcome_ms)
|
|
: 'Unavailable'}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
);
|
|
}
|
|
|
|
function LifecycleDetails({ item }) {
|
|
const executionTiming = formatExecutionTiming(item.execution?.timing);
|
|
const makerTerms = formatMakerTerms(item.maker_terms);
|
|
const inventoryCheck = formatInventoryCheck(item.inventory_check);
|
|
|
|
return (
|
|
<div className="lifecycle-detail-panel">
|
|
<div className="lifecycle-stage-grid">
|
|
<StageCard at={item.quote_observed_at} status={item.quote_observed_at ? 'Quote observed' : 'Missing quote event'} title="1. Quote came in">
|
|
<div>{formatTerms(item.request_terms)}</div>
|
|
<div className="status-subtle mono">{item.pair || 'pair unavailable'}</div>
|
|
</StageCard>
|
|
|
|
<StageCard at={item.decision_at} status={strategyDecisionStatus(item.decision)} title="2. Strategy decided">
|
|
<div>{plainCodeLabel(item.decision?.decision_reason || item.reason_code, 'No decision reason recorded')}</div>
|
|
<div className="status-subtle">{formatGrossEdgePct(item.gross_edge_pct)}</div>
|
|
<div className="status-subtle">{formatConfiguredEdgeBps(item.edge_bps)}</div>
|
|
<div className="status-subtle">{notionalLabel(item)}</div>
|
|
{inventoryCheck ? <div className="status-subtle">{inventoryCheck}</div> : null}
|
|
</StageCard>
|
|
|
|
<StageCard at={item.command_at} status={item.command_id ? 'Command recorded' : 'No command'} title="3. Executor command">
|
|
<IdentifierRow label="Command" value={item.command_id} />
|
|
<div>{makerTerms || formatTerms(item.submitted_terms)}</div>
|
|
{makerTerms ? <div className="status-subtle">{formatTerms(item.submitted_terms)}</div> : null}
|
|
</StageCard>
|
|
|
|
<StageCard at={item.execution_result_at} status={item.execution?.status || 'No relay result'} title="4. Relay response">
|
|
<div>{item.execution?.result_code || 'No executor result code stored'}</div>
|
|
{item.execution?.error_message ? <div className="status-subtle">{item.execution.error_message}</div> : null}
|
|
{!item.execution?.error_message && item.execution?.note ? <div className="status-subtle">{item.execution.note}</div> : null}
|
|
{executionTiming ? <div className="status-subtle">{executionTiming}</div> : null}
|
|
{item.execution?.status === 'submitted' ? (
|
|
<div className="status-subtle">Submitted means the relay accepted the response; it does not prove a trade.</div>
|
|
) : null}
|
|
</StageCard>
|
|
|
|
<StageCard at={item.outcome_observed_at} status={item.outcome_status || item.lifecycle_state} title="5. Outcome and settlement">
|
|
<div>{item.reason_text}</div>
|
|
<div className="status-subtle">{item.settlement_summary?.text || 'No settled inventory delta is linked to this quote.'}</div>
|
|
{item.settlement_summary?.caveat ? <div className="status-subtle">{item.settlement_summary.caveat}</div> : null}
|
|
</StageCard>
|
|
</div>
|
|
|
|
<div className="trace-block">
|
|
<IdentifierRow label="Quote" value={item.quote_id} />
|
|
<IdentifierRow label="Decision" value={item.decision_id} />
|
|
<IdentifierRow label="Command" value={item.command_id} />
|
|
</div>
|
|
|
|
<TimingWaterfall timing={item.maker_timing} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function pairDisplayLabel(pairId, pairConfig) {
|
|
const pair = (pairConfig?.pairs || []).find((entry) => (
|
|
(entry.pair_id || entry.pairId || entry.pair) === pairId
|
|
));
|
|
if (!pair) return truncateMiddle(pairId || 'Unknown pair', 42);
|
|
return `${pair.asset_in_symbol || pair.asset_in || pair.assetIn} -> ${pair.asset_out_symbol || pair.asset_out || pair.assetOut}`;
|
|
}
|
|
|
|
function fixedRows(items, count) {
|
|
const rows = (items || []).slice(0, count);
|
|
while (rows.length < count) rows.push(null);
|
|
return rows;
|
|
}
|
|
|
|
function MakerCompetitivenessSection({ summary, pairConfig }) {
|
|
const latestSummaryRef = useRef(summary || {});
|
|
const [displaySummary, setDisplaySummary] = useState(() => summary || {});
|
|
const [updatesPaused, setUpdatesPaused] = useState(false);
|
|
const total = displaySummary?.total || {};
|
|
const groups = displaySummary?.groups || [];
|
|
const ageBuckets = displaySummary?.age_buckets || [];
|
|
const latestErrors = displaySummary?.latest_errors || [];
|
|
const policySkips = displaySummary?.policy_skips || [];
|
|
const latencyStages = displaySummary?.latency_stages || [];
|
|
const groupRows = fixedRows(groups, COMPETITIVENESS_GROUP_ROW_COUNT);
|
|
const ageBucketRows = fixedRows(ageBuckets, COMPETITIVENESS_GROUP_ROW_COUNT);
|
|
const latestErrorRows = fixedRows(latestErrors, COMPETITIVENESS_DETAIL_ROW_COUNT);
|
|
const policySkipRows = fixedRows(policySkips, COMPETITIVENESS_DETAIL_ROW_COUNT);
|
|
const latencyStageRows = fixedRows(latencyStages, COMPETITIVENESS_LATENCY_ROW_COUNT);
|
|
|
|
useEffect(() => {
|
|
latestSummaryRef.current = summary || {};
|
|
if (!updatesPaused) setDisplaySummary(summary || {});
|
|
}, [summary, updatesPaused]);
|
|
|
|
function toggleUpdatesPaused() {
|
|
setUpdatesPaused((current) => {
|
|
const nextPaused = !current;
|
|
if (current) {
|
|
setDisplaySummary(latestSummaryRef.current || {});
|
|
}
|
|
return nextPaused;
|
|
});
|
|
}
|
|
|
|
return (
|
|
<section className="panel">
|
|
<div className="panel-head">
|
|
<div>
|
|
<div className="eyebrow">Maker competitiveness</div>
|
|
<h3>Response timing and quote-age outcomes</h3>
|
|
<div className="panel-subtitle">
|
|
Pair-native response evidence from durable quote, decision, command, executor result, and outcome rows.
|
|
</div>
|
|
</div>
|
|
<div className="pills">
|
|
<Pill label={`${total.count || 0} rows`} stateLabel="info" />
|
|
<Pill label={`${total.quote_not_found_or_finished_count || 0} stale/finished`} stateLabel={(total.quote_not_found_or_finished_count || 0) > 0 ? 'warning' : 'unknown'} />
|
|
<Pill label={updatesPaused ? 'Updates paused' : 'Live updates'} stateLabel={updatesPaused ? 'warning' : 'healthy'} />
|
|
<button
|
|
aria-pressed={updatesPaused}
|
|
className="button secondary trace-copy-button"
|
|
onClick={toggleUpdatesPaused}
|
|
type="button"
|
|
>
|
|
{updatesPaused ? 'Resume updates' : 'Pause updates'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="status-subtle competitiveness-snapshot-note">
|
|
Live rows update as they arrive. Fixed row slots and clamped cells keep the table height stable.
|
|
</div>
|
|
<div className="metric-stack">
|
|
<MetricCard label="Accepted" meta={formatRate(total.accepted_rate)} value={String(total.accepted_count || 0)} />
|
|
<MetricCard label="Relay failed" meta="Executor reached result" value={String(total.relay_failed_count || 0)} />
|
|
<MetricCard label="Already finished" meta={formatRate(total.stale_or_finished_rate)} value={String(total.quote_not_found_or_finished_count || 0)} />
|
|
<MetricCard label="Policy skips" meta="No relay submission" value={String(total.policy_skip_count || 0)} />
|
|
</div>
|
|
|
|
<TableFrame className="competitiveness-frame">
|
|
<table className="competitiveness-table competitiveness-groups-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Pair</th>
|
|
<th>Direction</th>
|
|
<th>Request</th>
|
|
<th>Edge</th>
|
|
<th>Result</th>
|
|
<th>Age / notional</th>
|
|
<th>Outcome</th>
|
|
<th>Counts</th>
|
|
<th>Quote to relay</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{groupRows.map((group, index) => {
|
|
if (!group) {
|
|
return (
|
|
<tr className="competitiveness-placeholder-row" key={`group-placeholder:${index}`}>
|
|
<td colSpan={9}>{index === 0 && !groups.length ? 'No competitiveness rows are available yet.' : ''}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
const quoteToRelay = group.latency_stages?.find((stage) => stage.stage === 'quote_to_relay_result_ms');
|
|
return (
|
|
<tr key={`${group.pair}:${group.direction}:${group.request_kind}:${group.edge_bps ?? 'edge'}:${group.result_code}:${group.quote_age_bucket}:${group.notional_bucket}:${group.outcome_status}`}>
|
|
<td>
|
|
<div>{pairDisplayLabel(group.pair, pairConfig)}</div>
|
|
<div className="status-subtle mono">{truncateMiddle(group.pair || '', 42)}</div>
|
|
</td>
|
|
<td>{plainCodeLabel(group.direction)}</td>
|
|
<td>{plainCodeLabel(group.request_kind)}</td>
|
|
<td>{group.edge_bps == null ? 'Unavailable' : `${group.edge_bps} bps`}</td>
|
|
<td>
|
|
<div>{plainCodeLabel(group.result_code)}</div>
|
|
{group.failure_category ? <div className="status-subtle">{plainCodeLabel(group.failure_category)}</div> : null}
|
|
</td>
|
|
<td>
|
|
<div>{group.quote_age_bucket}</div>
|
|
<div className="status-subtle">{group.notional_bucket}</div>
|
|
</td>
|
|
<td>{plainCodeLabel(group.outcome_status)}</td>
|
|
<td>
|
|
<div>{group.count}</div>
|
|
<div className="status-subtle">{`${group.accepted_count || 0} accepted / ${group.policy_skip_count || 0} skipped`}</div>
|
|
</td>
|
|
<td>
|
|
<div>{formatTimingMs(quoteToRelay?.p50_ms) || 'Unavailable'}</div>
|
|
<div className="status-subtle">{`p90 ${formatTimingMs(quoteToRelay?.p90_ms) || 'Unavailable'}`}</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
|
|
<div className="stack-grid competitiveness-table-stack">
|
|
<TableFrame className="competitiveness-frame">
|
|
<table className="competitiveness-table competitiveness-latency-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Latency stage</th>
|
|
<th>p50</th>
|
|
<th>p90</th>
|
|
<th>p99</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{latencyStageRows.map((stage, index) => {
|
|
if (!stage) {
|
|
return (
|
|
<tr className="competitiveness-placeholder-row" key={`latency-stage-placeholder:${index}`}>
|
|
<td colSpan={4}>{index === 0 && !latencyStages.length ? 'No stage timing percentiles are available yet.' : ''}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
return (
|
|
<tr key={stage.stage}>
|
|
<td>{stageLabel(stage.stage)}</td>
|
|
<td>{formatTimingMs(stage.p50_ms)}</td>
|
|
<td>{formatTimingMs(stage.p90_ms)}</td>
|
|
<td>{formatTimingMs(stage.p99_ms)}</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
|
|
<TableFrame className="competitiveness-frame">
|
|
<table className="competitiveness-table competitiveness-age-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Age bucket</th>
|
|
<th>Outcome</th>
|
|
<th>Count</th>
|
|
<th>Accepted</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{ageBucketRows.map((bucket, index) => {
|
|
if (!bucket) {
|
|
return (
|
|
<tr className="competitiveness-placeholder-row" key={`age-bucket-placeholder:${index}`}>
|
|
<td colSpan={4}>{index === 0 && !ageBuckets.length ? 'No quote-age buckets are available yet.' : ''}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
return (
|
|
<tr key={`${bucket.pair}:${bucket.quote_age_bucket}:${bucket.outcome_status}:${bucket.notional_bucket || ''}`}>
|
|
<td>
|
|
<div>{bucket.quote_age_bucket}</div>
|
|
<div className="status-subtle">{pairDisplayLabel(bucket.pair, pairConfig)}</div>
|
|
</td>
|
|
<td>{plainCodeLabel(bucket.outcome_status)}</td>
|
|
<td>{bucket.count}</td>
|
|
<td>{bucket.accepted_count || 0}</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
</div>
|
|
|
|
<div className="stack-grid competitiveness-table-stack">
|
|
<TableFrame className="competitiveness-frame">
|
|
<table className="competitiveness-table competitiveness-detail-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Latest relay errors</th>
|
|
<th>Quote age</th>
|
|
<th>Error</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{latestErrorRows.map((error, index) => {
|
|
if (!error) {
|
|
return (
|
|
<tr className="competitiveness-placeholder-row" key={`latest-error-placeholder:${index}`}>
|
|
<td colSpan={3}>{index === 0 && !latestErrors.length ? 'No relay errors are available yet.' : ''}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
return (
|
|
<tr key={`${error.quote_id}:${error.result_at}`}>
|
|
<td>
|
|
<IdentifierRow label="Quote" value={error.quote_id} />
|
|
<div className="status-subtle">{pairDisplayLabel(error.pair, pairConfig)}</div>
|
|
<div className="status-subtle">{plainCodeLabel(error.failure_category || error.result_code)}</div>
|
|
</td>
|
|
<td>
|
|
<div>{formatTimingMs(error.quote_age_ms) || 'Unavailable'}</div>
|
|
<div className="status-subtle">{error.quote_age_bucket}</div>
|
|
</td>
|
|
<td>
|
|
<div className="competitiveness-error-text" title={error.error_message || ''}>
|
|
{error.error_message || 'Error text unavailable'}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
|
|
<TableFrame className="competitiveness-frame">
|
|
<table className="competitiveness-table competitiveness-detail-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Policy skips</th>
|
|
<th>Age</th>
|
|
<th>Config</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{policySkipRows.map((skip, index) => {
|
|
if (!skip) {
|
|
return (
|
|
<tr className="competitiveness-placeholder-row" key={`policy-skip-placeholder:${index}`}>
|
|
<td colSpan={3}>{index === 0 && !policySkips.length ? 'No policy skips are available yet.' : ''}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
return (
|
|
<tr key={`${skip.quote_id}:${skip.decision_at}`}>
|
|
<td>
|
|
<IdentifierRow label="Quote" value={skip.quote_id} />
|
|
<div className="status-subtle">{plainCodeLabel(skip.reason_code)}</div>
|
|
</td>
|
|
<td>
|
|
<div>{formatTimingMs(skip.quote_age_ms) || 'Unavailable'}</div>
|
|
<div className="status-subtle">{`max ${formatTimingMs(skip.max_quote_age_ms) || 'Unavailable'}`}</div>
|
|
</td>
|
|
<td>
|
|
<div>{skip.pair_config_version ? `v${skip.pair_config_version}` : 'Version unavailable'}</div>
|
|
<div className="status-subtle mono">{truncateMiddle(skip.pair_config_id || '', 36)}</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function QuoteLifecycleTable({ items }) {
|
|
const [expanded, setExpanded] = useState(() => new Set());
|
|
const [showStrategyRejected, setShowStrategyRejected] = useState(true);
|
|
const latestItemsRef = useRef(items || []);
|
|
const [quoteDisplayPaused, setQuoteDisplayPaused] = useState(false);
|
|
const [displayItems, setDisplayItems] = useState(() => items || []);
|
|
const liveNow = useNow();
|
|
const [displayNow, setDisplayNow] = useState(() => Date.now());
|
|
|
|
useEffect(() => {
|
|
latestItemsRef.current = items || [];
|
|
if (!quoteDisplayPaused) setDisplayItems(items || []);
|
|
}, [items, quoteDisplayPaused]);
|
|
|
|
useEffect(() => {
|
|
if (!quoteDisplayPaused) setDisplayNow(liveNow);
|
|
}, [liveNow, quoteDisplayPaused]);
|
|
|
|
const rejectedCount = useMemo(
|
|
() => displayItems.filter((item) => isStrategyRejected(item)).length,
|
|
[displayItems],
|
|
);
|
|
const visibleItems = useMemo(
|
|
() => (showStrategyRejected ? displayItems : displayItems.filter((item) => !isStrategyRejected(item))),
|
|
[displayItems, showStrategyRejected],
|
|
);
|
|
const visibleRows = fixedRows(visibleItems, QUOTE_LIFECYCLE_ROW_COUNT);
|
|
const emptyRowsMessage = !displayItems.length
|
|
? 'No quote lifecycle evidence has been observed yet.'
|
|
: !visibleItems.length
|
|
? 'No quote lifecycle rows match the current filters.'
|
|
: '';
|
|
|
|
function toggle(rowKey) {
|
|
setExpanded((current) => {
|
|
const next = new Set(current);
|
|
if (next.has(rowKey)) next.delete(rowKey);
|
|
else next.add(rowKey);
|
|
return next;
|
|
});
|
|
}
|
|
|
|
function applyLatestLifecycleDisplay() {
|
|
setDisplayItems(latestItemsRef.current || []);
|
|
setDisplayNow(Date.now());
|
|
}
|
|
|
|
function toggleQuoteDisplayPaused() {
|
|
if (quoteDisplayPaused) applyLatestLifecycleDisplay();
|
|
setQuoteDisplayPaused((paused) => !paused);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="quote-lifecycle-controls">
|
|
<label className="toggle-field">
|
|
<input
|
|
checked={showStrategyRejected}
|
|
onChange={(event) => setShowStrategyRejected(event.target.checked)}
|
|
type="checkbox"
|
|
/>
|
|
<span>{`Rejected by strategy (${rejectedCount})`}</span>
|
|
</label>
|
|
<button
|
|
aria-pressed={quoteDisplayPaused}
|
|
className="button secondary"
|
|
onClick={toggleQuoteDisplayPaused}
|
|
type="button"
|
|
>
|
|
{quoteDisplayPaused ? 'Resume display' : 'Pause display'}
|
|
</button>
|
|
<Pill label={quoteDisplayPaused ? 'Display paused' : 'Live updates'} stateLabel={quoteDisplayPaused ? 'warning' : 'healthy'} />
|
|
</div>
|
|
<div className="status-subtle quote-lifecycle-snapshot-note">
|
|
Live rows update as they arrive. Fixed row slots and clamped cells keep the table height stable.
|
|
</div>
|
|
|
|
<TableFrame className="quote-lifecycle-frame">
|
|
<table className="quote-lifecycle-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Quote time</th>
|
|
<th>Quote id</th>
|
|
<th>Request</th>
|
|
<th>Responded?</th>
|
|
<th>Result</th>
|
|
<th>Reason</th>
|
|
<th>Gross edge / notional</th>
|
|
<th>Lifecycle</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{visibleRows.map((item, index) => {
|
|
if (!item) {
|
|
return (
|
|
<tr className="quote-lifecycle-placeholder-row" key={`quote-lifecycle-placeholder:${index}`}>
|
|
<td colSpan={8}>{index === 0 ? emptyRowsMessage : ''}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
const rowKey = item.quote_id || item.decision_id || item.command_id || item.latest_stage_at || String(index);
|
|
const isExpanded = expanded.has(rowKey);
|
|
const quoteTime = item.quote_activity_at || item.latest_stage_at;
|
|
const updatedText = item.latest_stage_at && item.latest_stage_at !== item.quote_activity_at
|
|
? `Updated ${formatTimestamp(item.latest_stage_at)} - ${formatRelativeAge(item.latest_stage_at, displayNow)}`
|
|
: '';
|
|
return (
|
|
<Fragment key={rowKey}>
|
|
<tr className={`quote-lifecycle-row${item.live_flash_at ? ' quote-row-flash' : ''}`} key={`${rowKey}:row`}>
|
|
<td>
|
|
<div className="quote-lifecycle-cell">
|
|
<div className="lifecycle-line lifecycle-clamp-one">{formatTimestamp(quoteTime)}</div>
|
|
<div className="status-subtle quote-age lifecycle-line lifecycle-clamp-one">{formatRelativeAge(quoteTime, displayNow)}</div>
|
|
<div className="status-subtle lifecycle-line lifecycle-clamp-two">{updatedText}</div>
|
|
</div>
|
|
</td>
|
|
<td><IdentifierRow label="Quote" value={item.quote_id} /></td>
|
|
<td>
|
|
<div className="quote-lifecycle-cell">
|
|
<div className="lifecycle-line lifecycle-clamp-two">{formatTerms(item.request_terms || item.submitted_terms)}</div>
|
|
<div className="status-subtle mono lifecycle-line lifecycle-clamp-one">{truncateMiddle(item.pair || '', 34)}</div>
|
|
</div>
|
|
</td>
|
|
<td>{responseLabel(item)}</td>
|
|
<td><Pill label={item.lifecycle_label} stateLabel={item.lifecycle_tone} /></td>
|
|
<td>
|
|
<div className="quote-lifecycle-cell">
|
|
<div className="lifecycle-line lifecycle-clamp-two">{item.reason_text}</div>
|
|
<div className="status-subtle mono lifecycle-line lifecycle-clamp-one">{item.reason_code || 'reason_unknown'}</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div className="quote-lifecycle-cell">
|
|
<div className={`lifecycle-line lifecycle-clamp-one ${Number(item.gross_edge_pct) > 0 ? 'value-positive' : Number(item.gross_edge_pct) < 0 ? 'value-negative' : ''}`}>{formatGrossEdgePct(item.gross_edge_pct)}</div>
|
|
<div className="status-subtle lifecycle-line lifecycle-clamp-one">{formatConfiguredEdgeBps(item.edge_bps)}</div>
|
|
<div className="status-subtle lifecycle-line lifecycle-clamp-one">{notionalLabel(item)}</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<button className="button secondary" onClick={() => toggle(rowKey)} type="button">
|
|
{isExpanded ? 'Hide lifecycle' : 'Show lifecycle'}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{isExpanded ? (
|
|
<tr className="lifecycle-expanded-row" key={`${rowKey}:details`}>
|
|
<td colSpan={8}><LifecycleDetails item={item} /></td>
|
|
</tr>
|
|
) : null}
|
|
</Fragment>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function SuccessfulTradesTable({ items }) {
|
|
const [expanded, setExpanded] = useState(() => new Set());
|
|
if (!items?.length) {
|
|
return (
|
|
<EmptyState>
|
|
No successful trades with linked settlement evidence yet. A submitted quote response is not counted here until a durable terminal outcome and settled asset movement are linked to the quote.
|
|
</EmptyState>
|
|
);
|
|
}
|
|
|
|
function toggle(rowKey) {
|
|
setExpanded((current) => {
|
|
const next = new Set(current);
|
|
if (next.has(rowKey)) next.delete(rowKey);
|
|
else next.add(rowKey);
|
|
return next;
|
|
});
|
|
}
|
|
|
|
return (
|
|
<TableFrame>
|
|
<table className="successful-trades-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Completed</th>
|
|
<th>Quote id</th>
|
|
<th>Gross edge</th>
|
|
<th>Gross edge est.</th>
|
|
<th>Settlement</th>
|
|
<th>Realized PnL</th>
|
|
<th>Lifecycle</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{items.map((item, index) => {
|
|
const rowKey = item.quote_id || item.command_id || item.latest_stage_at || String(index);
|
|
const isExpanded = expanded.has(rowKey);
|
|
return (
|
|
<Fragment key={rowKey}>
|
|
<tr key={`${rowKey}:trade`}>
|
|
<td>{formatTimestamp(item.latest_stage_at)}</td>
|
|
<td><IdentifierRow label="Quote" value={item.quote_id} /></td>
|
|
<td>
|
|
<div>{formatGrossEdgePct(item.gross_edge_pct)}</div>
|
|
<div className="status-subtle">{formatConfiguredEdgeBps(item.edge_bps)}</div>
|
|
<div className="status-subtle">{notionalLabel(item)}</div>
|
|
</td>
|
|
<td>
|
|
<div>{grossEdgeEstimate(item)}</div>
|
|
<div className="status-subtle">Estimate from edge x notional, not realized PnL.</div>
|
|
</td>
|
|
<td>
|
|
<div>{item.settlement_summary?.text || 'No settled inventory delta is linked to this quote.'}</div>
|
|
{item.settlement_summary?.method ? <div className="status-subtle mono">{item.settlement_summary.method}</div> : null}
|
|
</td>
|
|
<td>
|
|
<div>Unavailable</div>
|
|
<div className="status-subtle">Fees and venue-native terminal fill are not stored.</div>
|
|
</td>
|
|
<td>
|
|
<button className="button secondary" onClick={() => toggle(rowKey)} type="button">
|
|
{isExpanded ? 'Hide lifecycle' : 'Show lifecycle'}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{isExpanded ? (
|
|
<tr className="lifecycle-expanded-row" key={`${rowKey}:trade-details`}>
|
|
<td colSpan={7}><LifecycleDetails item={item} /></td>
|
|
</tr>
|
|
) : null}
|
|
</Fragment>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
);
|
|
}
|
|
|
|
function AssetCatalogSection({ assetCatalog, onControl }) {
|
|
const latest = assetCatalog?.latest_import || null;
|
|
const counts = assetCatalog?.counts || {};
|
|
const items = assetCatalog?.items || [];
|
|
|
|
return (
|
|
<section className="panel">
|
|
<div className="panel-head">
|
|
<div>
|
|
<div className="eyebrow">Asset registry</div>
|
|
<h3>Supported-token import status</h3>
|
|
<div className="panel-subtitle">
|
|
Last import {latest?.fetched_at ? formatTimestamp(latest.fetched_at) : 'not run'}
|
|
</div>
|
|
</div>
|
|
<div className="pills">
|
|
<Pill label={latest?.status || 'not imported'} stateLabel={latest?.status === 'success' ? 'healthy' : 'warning'} />
|
|
<button className="button secondary" onClick={() => onControl?.('operator-dashboard', 'import-supported-assets')} type="button">
|
|
Import assets
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="metric-grid">
|
|
<MetricCard label="Known assets" meta={`${counts.inventory_enabled || 0} inventory tracked`} value={String(counts.known || 0)} />
|
|
<MetricCard label="Supported now" meta={`${latest?.token_count || 0} tokens in latest run`} value={String(counts.supported || 0)} />
|
|
<MetricCard label="Retired" meta="Kept for balances and history" value={String(counts.retired || 0)} />
|
|
</div>
|
|
<TableFrame>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Asset</th>
|
|
<th>Decimals</th>
|
|
<th>Chain</th>
|
|
<th>Deposit</th>
|
|
<th>Price</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{items.length ? items.map((asset) => (
|
|
<tr key={asset.asset_id || asset.assetId}>
|
|
<td>
|
|
<div>{asset.label || asset.symbol}</div>
|
|
<div className="status-subtle mono">{truncateMiddle(asset.asset_id || asset.assetId, 42)}</div>
|
|
</td>
|
|
<td>{asset.decimals}</td>
|
|
<td>{asset.blockchain || asset.chain || 'Unavailable'}</td>
|
|
<td>
|
|
{asset.deposit_address || asset.depositAddress ? (
|
|
<>
|
|
<div className="trace-row">
|
|
<span
|
|
className="mono trace-id"
|
|
title={asset.deposit_address || asset.depositAddress}
|
|
>
|
|
{truncateMiddle(asset.deposit_address || asset.depositAddress, 34)}
|
|
</span>
|
|
<button
|
|
className="button secondary trace-copy-button"
|
|
onClick={() => copyIdentifier(asset.deposit_address || asset.depositAddress)}
|
|
type="button"
|
|
>
|
|
Copy
|
|
</button>
|
|
</div>
|
|
<div className="status-subtle">
|
|
{asset.deposit_memo || asset.depositMemo ? `Memo ${asset.deposit_memo || asset.depositMemo}` : asset.deposit_chain || asset.depositChain || 'Deposit handle'}
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="status-subtle">Unavailable</div>
|
|
)}
|
|
</td>
|
|
<td>{asset.latest_price || asset.latestPrice || 'Unavailable'}</td>
|
|
<td>
|
|
<Pill
|
|
label={asset.supported ? 'supported' : 'retired'}
|
|
stateLabel={asset.supported ? 'healthy' : 'warning'}
|
|
/>
|
|
</td>
|
|
</tr>
|
|
)) : (
|
|
<tr><td colSpan={6}>No DB asset registry rows are available.</td></tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function assetOptionLabel(asset) {
|
|
return `${asset.label || asset.symbol || asset.asset_id || asset.assetId} - ${truncateMiddle(asset.asset_id || asset.assetId, 34)}`;
|
|
}
|
|
|
|
function PairConfigSection({ assetCatalog, pairConfig, onControl }) {
|
|
const pairs = pairConfig?.pairs || [];
|
|
const assets = useMemo(() => (assetCatalog?.items || [])
|
|
.filter((asset) => asset.asset_id || asset.assetId)
|
|
.sort((left, right) => String(left.label || left.symbol || left.asset_id || '').localeCompare(
|
|
String(right.label || right.symbol || right.asset_id || ''),
|
|
)), [assetCatalog?.items]);
|
|
const [pairForm, setPairForm] = useState({
|
|
asset_in: '',
|
|
asset_out: '',
|
|
mode: 'observe_only',
|
|
edge_bps: '49',
|
|
min_notional: '0',
|
|
max_notional: '150',
|
|
});
|
|
const [edgeDrafts, setEdgeDrafts] = useState({});
|
|
const [minNotionalDrafts, setMinNotionalDrafts] = useState({});
|
|
const [maxNotionalDrafts, setMaxNotionalDrafts] = useState({});
|
|
const [policyEnabledDrafts, setPolicyEnabledDrafts] = useState({});
|
|
const [maxQuoteAgeDrafts, setMaxQuoteAgeDrafts] = useState({});
|
|
|
|
useEffect(() => {
|
|
if (!assets.length) return;
|
|
setPairForm((current) => ({
|
|
...current,
|
|
asset_in: current.asset_in || assets[0]?.asset_id || assets[0]?.assetId || '',
|
|
asset_out: current.asset_out || assets[1]?.asset_id || assets[1]?.assetId || assets[0]?.asset_id || assets[0]?.assetId || '',
|
|
}));
|
|
}, [assets]);
|
|
|
|
useEffect(() => {
|
|
setEdgeDrafts(Object.fromEntries(pairs.map((pair) => {
|
|
const pairId = pair.pair_id || pair.pairId;
|
|
const strategyConfig = pair.strategyConfig || pair.strategy_config || {};
|
|
const tradingMode = TRADING_PAIR_MODES.has(pair.mode);
|
|
return [pairId, String(strategyConfig.edge_bps ?? pair.edge_bps ?? (tradingMode ? '49' : ''))];
|
|
})));
|
|
setMaxNotionalDrafts(Object.fromEntries(pairs.map((pair) => {
|
|
const pairId = pair.pair_id || pair.pairId;
|
|
const strategyConfig = pair.strategyConfig || pair.strategy_config || {};
|
|
const tradingMode = TRADING_PAIR_MODES.has(pair.mode);
|
|
return [pairId, String(strategyConfig.max_notional ?? pair.max_notional ?? (tradingMode ? '150' : ''))];
|
|
})));
|
|
setMinNotionalDrafts(Object.fromEntries(pairs.map((pair) => {
|
|
const pairId = pair.pair_id || pair.pairId;
|
|
const strategyConfig = pair.strategyConfig || pair.strategy_config || {};
|
|
const tradingMode = TRADING_PAIR_MODES.has(pair.mode);
|
|
return [pairId, String(strategyConfig.min_notional ?? pair.min_notional ?? (tradingMode ? '0' : ''))];
|
|
})));
|
|
setPolicyEnabledDrafts(Object.fromEntries(pairs.map((pair) => {
|
|
const pairId = pair.pair_id || pair.pairId;
|
|
const strategyConfig = pair.strategyConfig || pair.strategy_config || {};
|
|
return [pairId, Boolean(
|
|
strategyConfig.maker_max_quote_age_enabled ?? strategyConfig.makerMaxQuoteAgeEnabled,
|
|
)];
|
|
})));
|
|
setMaxQuoteAgeDrafts(Object.fromEntries(pairs.map((pair) => {
|
|
const pairId = pair.pair_id || pair.pairId;
|
|
const strategyConfig = pair.strategyConfig || pair.strategy_config || {};
|
|
return [pairId, String(
|
|
strategyConfig.maker_max_quote_age_ms ?? strategyConfig.makerMaxQuoteAgeMs ?? '',
|
|
)];
|
|
})));
|
|
}, [pairs]);
|
|
|
|
async function updatePairConfig(pair) {
|
|
const pairId = pair.pair_id || pair.pairId;
|
|
const strategyConfig = pair.strategyConfig || pair.strategy_config || {};
|
|
const hasStrategyConfig = Boolean(strategyConfig.config_id || strategyConfig.configId);
|
|
const edgeBps = edgeDrafts[pairId];
|
|
const maxNotional = maxNotionalDrafts[pairId];
|
|
const minNotional = minNotionalDrafts[pairId];
|
|
const policyEnabled = policyEnabledDrafts[pairId] === true;
|
|
const maxQuoteAgeMs = maxQuoteAgeDrafts[pairId];
|
|
if (!edgeBps || !maxNotional || minNotional === undefined || minNotional === '') return;
|
|
if (policyEnabled && !maxQuoteAgeMs) return;
|
|
|
|
if (!hasStrategyConfig) {
|
|
const mode = pair.mode || pair.status || 'observe_only';
|
|
if (
|
|
TRADING_PAIR_MODES.has(mode)
|
|
&& !window.confirm('Initialize strategy config for this trading pair?')
|
|
) {
|
|
return;
|
|
}
|
|
await onControl?.('operator-dashboard', 'set-pair-mode', {
|
|
pair_id: pairId,
|
|
mode,
|
|
edge_bps: Number(edgeBps),
|
|
max_notional: maxNotional,
|
|
min_notional: minNotional,
|
|
maker_max_quote_age_enabled: policyEnabled,
|
|
maker_max_quote_age_ms: policyEnabled ? Number(maxQuoteAgeMs) : null,
|
|
maker_latency_policy_reason: policyEnabled ? 'operator dashboard maker response-age policy' : null,
|
|
});
|
|
return;
|
|
}
|
|
|
|
await onControl?.('operator-dashboard', 'update-pair-edge', {
|
|
pair_id: pairId,
|
|
edge_bps: Number(edgeBps),
|
|
max_notional: maxNotional,
|
|
min_notional: minNotional,
|
|
maker_max_quote_age_enabled: policyEnabled,
|
|
maker_max_quote_age_ms: policyEnabled ? Number(maxQuoteAgeMs) : null,
|
|
maker_latency_policy_reason: policyEnabled ? 'operator dashboard maker response-age policy' : null,
|
|
});
|
|
}
|
|
|
|
async function applyPairMode(event) {
|
|
event.preventDefault();
|
|
if (!pairForm.asset_in || !pairForm.asset_out || pairForm.asset_in === pairForm.asset_out) return;
|
|
if (
|
|
TRADING_PAIR_MODES.has(pairForm.mode)
|
|
&& !window.confirm('Activate trading mode for this directed pair?')
|
|
) {
|
|
return;
|
|
}
|
|
await onControl?.('operator-dashboard', 'set-pair-mode', {
|
|
asset_in: pairForm.asset_in,
|
|
asset_out: pairForm.asset_out,
|
|
mode: pairForm.mode,
|
|
edge_bps: Number(pairForm.edge_bps),
|
|
max_notional: pairForm.max_notional,
|
|
min_notional: pairForm.min_notional,
|
|
});
|
|
}
|
|
|
|
async function pausePair(pair) {
|
|
await onControl?.('operator-dashboard', 'pause-pair', {
|
|
pair_id: pair.pair_id || pair.pairId,
|
|
});
|
|
}
|
|
|
|
async function activatePair(pair) {
|
|
const pairId = pair.pair_id || pair.pairId;
|
|
const nextMode = ['maker', 'taker', 'both'].includes(pair.mode) ? pair.mode : 'observe_only';
|
|
if (
|
|
TRADING_PAIR_MODES.has(nextMode)
|
|
&& !window.confirm('Reactivate trading mode for this directed pair?')
|
|
) {
|
|
return;
|
|
}
|
|
await onControl?.('operator-dashboard', 'set-pair-mode', {
|
|
pair_id: pairId,
|
|
mode: nextMode,
|
|
});
|
|
}
|
|
|
|
const tradingModeSelected = TRADING_PAIR_MODES.has(pairForm.mode);
|
|
const pairFormDisabled = !assets.length
|
|
|| pairForm.asset_in === pairForm.asset_out
|
|
|| (tradingModeSelected && (!pairForm.edge_bps || !pairForm.max_notional || pairForm.min_notional === ''));
|
|
|
|
return (
|
|
<section className="panel">
|
|
<div className="panel-head">
|
|
<div>
|
|
<div className="eyebrow">Pair config</div>
|
|
<h3>Add, pause, and tune directed pairs</h3>
|
|
<div className="panel-subtitle">
|
|
Loaded {pairConfig?.loaded_at ? formatTimestamp(pairConfig.loaded_at) : 'unavailable'}
|
|
</div>
|
|
</div>
|
|
<div className="pills">
|
|
<Pill label={pairConfig?.ok ? 'config loaded' : pairConfig?.block_reason || 'blocked'} stateLabel={pairConfig?.ok ? 'healthy' : 'warning'} />
|
|
</div>
|
|
</div>
|
|
<form onSubmit={applyPairMode}>
|
|
<div className="form-grid">
|
|
<div className="field">
|
|
<label htmlFor="pair-asset-in">Asset in</label>
|
|
<select
|
|
id="pair-asset-in"
|
|
onChange={(event) => setPairForm((current) => ({ ...current, asset_in: event.target.value }))}
|
|
value={pairForm.asset_in}
|
|
>
|
|
{assets.map((asset) => {
|
|
const assetId = asset.asset_id || asset.assetId;
|
|
return <option key={assetId} value={assetId}>{assetOptionLabel(asset)}</option>;
|
|
})}
|
|
</select>
|
|
</div>
|
|
<div className="field">
|
|
<label htmlFor="pair-asset-out">Asset out</label>
|
|
<select
|
|
id="pair-asset-out"
|
|
onChange={(event) => setPairForm((current) => ({ ...current, asset_out: event.target.value }))}
|
|
value={pairForm.asset_out}
|
|
>
|
|
{assets.map((asset) => {
|
|
const assetId = asset.asset_id || asset.assetId;
|
|
return <option key={assetId} value={assetId}>{assetOptionLabel(asset)}</option>;
|
|
})}
|
|
</select>
|
|
</div>
|
|
<div className="field">
|
|
<label htmlFor="pair-mode">Mode</label>
|
|
<select
|
|
id="pair-mode"
|
|
onChange={(event) => setPairForm((current) => ({ ...current, mode: event.target.value }))}
|
|
value={pairForm.mode}
|
|
>
|
|
<option value="observe_only">Observe only</option>
|
|
<option value="maker">Maker</option>
|
|
<option value="taker">Taker</option>
|
|
<option value="both">Maker and taker</option>
|
|
</select>
|
|
</div>
|
|
<div className="field">
|
|
<label htmlFor="pair-edge-bps">Edge bps</label>
|
|
<input
|
|
disabled={!tradingModeSelected}
|
|
id="pair-edge-bps"
|
|
min="1"
|
|
onChange={(event) => setPairForm((current) => ({ ...current, edge_bps: event.target.value }))}
|
|
required={tradingModeSelected}
|
|
step="1"
|
|
type="number"
|
|
value={pairForm.edge_bps}
|
|
/>
|
|
</div>
|
|
<div className="field">
|
|
<label htmlFor="pair-min-notional">Min notional</label>
|
|
<input
|
|
disabled={!tradingModeSelected}
|
|
id="pair-min-notional"
|
|
min="0"
|
|
onChange={(event) => setPairForm((current) => ({ ...current, min_notional: event.target.value }))}
|
|
required={tradingModeSelected}
|
|
step="0.00000001"
|
|
type="number"
|
|
value={pairForm.min_notional}
|
|
/>
|
|
</div>
|
|
<div className="field">
|
|
<label htmlFor="pair-max-notional">Max notional</label>
|
|
<input
|
|
disabled={!tradingModeSelected}
|
|
id="pair-max-notional"
|
|
min="0.00000001"
|
|
onChange={(event) => setPairForm((current) => ({ ...current, max_notional: event.target.value }))}
|
|
required={tradingModeSelected}
|
|
step="0.00000001"
|
|
type="number"
|
|
value={pairForm.max_notional}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="button-row">
|
|
<button className="button" disabled={pairFormDisabled} type="submit">
|
|
Add / activate pair
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<TableFrame>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Pair</th>
|
|
<th>Mode</th>
|
|
<th>Configured edge</th>
|
|
<th>Limits</th>
|
|
<th>Route</th>
|
|
<th>Blocked</th>
|
|
<th>Config</th>
|
|
<th>Controls</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{pairs.length ? pairs.map((pair) => {
|
|
const pairId = pair.pair_id || pair.pairId;
|
|
const strategyConfig = pair.strategyConfig || pair.strategy_config || {};
|
|
const route = pair.priceRoute || pair.price_route || {};
|
|
const hasStrategyConfig = Boolean(strategyConfig.config_id || strategyConfig.configId);
|
|
const tradingMode = TRADING_PAIR_MODES.has(pair.mode);
|
|
const policyEnabled = policyEnabledDrafts[pairId] === true;
|
|
const configButtonDisabled = !edgeDrafts[pairId]
|
|
|| minNotionalDrafts[pairId] === undefined
|
|
|| minNotionalDrafts[pairId] === ''
|
|
|| !maxNotionalDrafts[pairId]
|
|
|| (policyEnabled && !maxQuoteAgeDrafts[pairId])
|
|
|| (!hasStrategyConfig && !tradingMode);
|
|
return (
|
|
<tr key={pairId}>
|
|
<td>
|
|
<div>{pair.asset_in_symbol || pair.asset_in} {'->'} {pair.asset_out_symbol || pair.asset_out}</div>
|
|
<div className="status-subtle mono">{truncateMiddle(pairId, 42)}</div>
|
|
</td>
|
|
<td><Pill label={pair.mode || pair.status} stateLabel={pair.canTrade || pair.can_trade ? 'healthy' : 'warning'} /></td>
|
|
<td>{formatConfiguredEdgeBps(strategyConfig.edge_bps, { prefix: false })}</td>
|
|
<td>
|
|
<div>{strategyConfig.min_notional ?? '0'} min</div>
|
|
<div>{strategyConfig.max_notional || 'Unavailable'} max</div>
|
|
<div className="status-subtle">
|
|
{strategyConfig.request_max_notional == null
|
|
? 'No request cap'
|
|
: `${strategyConfig.request_max_notional} request max`}
|
|
</div>
|
|
<div className="status-subtle">
|
|
{strategyConfig.request_max_slippage_bps == null
|
|
? 'No slippage cap'
|
|
: `${strategyConfig.request_max_slippage_bps} bps slippage max`}
|
|
</div>
|
|
<div className="status-subtle">{strategyConfig.price_max_age_ms || 'Unavailable'} ms price max age</div>
|
|
<div className="status-subtle">
|
|
{strategyConfig.maker_max_quote_age_enabled || strategyConfig.makerMaxQuoteAgeEnabled
|
|
? `${strategyConfig.maker_max_quote_age_ms ?? strategyConfig.makerMaxQuoteAgeMs} ms response max age`
|
|
: 'Response age policy disabled'}
|
|
</div>
|
|
</td>
|
|
<td>{route.source || 'Unavailable'}</td>
|
|
<td>{pair.blockReason || pair.block_reason || 'No'}</td>
|
|
<td>
|
|
<div>v{strategyConfig.version || 'Unavailable'}</div>
|
|
<div className="trace-row">
|
|
<span className="status-subtle">Edge</span>
|
|
<input
|
|
aria-label={`Edge bps for ${pairId}`}
|
|
min="1"
|
|
onChange={(event) => setEdgeDrafts((current) => ({
|
|
...current,
|
|
[pairId]: event.target.value,
|
|
}))}
|
|
step="1"
|
|
style={{ maxWidth: 92 }}
|
|
type="number"
|
|
value={edgeDrafts[pairId] ?? ''}
|
|
/>
|
|
</div>
|
|
<div className="trace-row">
|
|
<span className="status-subtle">Min</span>
|
|
<input
|
|
aria-label={`Min notional for ${pairId}`}
|
|
min="0"
|
|
onChange={(event) => setMinNotionalDrafts((current) => ({
|
|
...current,
|
|
[pairId]: event.target.value,
|
|
}))}
|
|
step="0.00000001"
|
|
style={{ maxWidth: 112 }}
|
|
type="number"
|
|
value={minNotionalDrafts[pairId] ?? ''}
|
|
/>
|
|
</div>
|
|
<div className="trace-row">
|
|
<span className="status-subtle">Max</span>
|
|
<input
|
|
aria-label={`Max notional for ${pairId}`}
|
|
min="0.00000001"
|
|
onChange={(event) => setMaxNotionalDrafts((current) => ({
|
|
...current,
|
|
[pairId]: event.target.value,
|
|
}))}
|
|
step="0.00000001"
|
|
style={{ maxWidth: 112 }}
|
|
type="number"
|
|
value={maxNotionalDrafts[pairId] ?? ''}
|
|
/>
|
|
</div>
|
|
<div className="trace-row">
|
|
<button
|
|
className="button secondary trace-copy-button"
|
|
disabled={configButtonDisabled}
|
|
onClick={() => updatePairConfig(pair)}
|
|
type="button"
|
|
>
|
|
{hasStrategyConfig ? 'Save' : 'Init'}
|
|
</button>
|
|
</div>
|
|
<div className="trace-row">
|
|
<label className="checkbox-row">
|
|
<input
|
|
aria-label={`Enable response age policy for ${pairId}`}
|
|
checked={policyEnabled}
|
|
onChange={(event) => setPolicyEnabledDrafts((current) => ({
|
|
...current,
|
|
[pairId]: event.target.checked,
|
|
}))}
|
|
type="checkbox"
|
|
/>
|
|
<span className="status-subtle">Age policy</span>
|
|
</label>
|
|
</div>
|
|
<div className="trace-row">
|
|
<span className="status-subtle">Max age</span>
|
|
<input
|
|
aria-label={`Max quote response age milliseconds for ${pairId}`}
|
|
disabled={!policyEnabled}
|
|
min="1"
|
|
onChange={(event) => setMaxQuoteAgeDrafts((current) => ({
|
|
...current,
|
|
[pairId]: event.target.value,
|
|
}))}
|
|
step="1"
|
|
style={{ maxWidth: 112 }}
|
|
type="number"
|
|
value={maxQuoteAgeDrafts[pairId] ?? ''}
|
|
/>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div className="button-row">
|
|
{pair.enabled && pair.status !== 'disabled' ? (
|
|
<button className="button secondary trace-copy-button" onClick={() => pausePair(pair)} type="button">
|
|
Pause
|
|
</button>
|
|
) : (
|
|
<button className="button secondary trace-copy-button" onClick={() => activatePair(pair)} type="button">
|
|
Activate
|
|
</button>
|
|
)}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
}) : (
|
|
<tr><td colSpan={8}>No directed pairs are configured.</td></tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</TableFrame>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function StrategyOverviewSection({ strategy }) {
|
|
const funnel = strategy.strategy_state.trade_funnel || {};
|
|
const counts = funnel.counts || {};
|
|
|
|
return (
|
|
<section className="panel">
|
|
<div className="panel-head">
|
|
<div>
|
|
<div className="eyebrow">Trading evidence</div>
|
|
<h2>Quotes, responses, and proven trades</h2>
|
|
<div className="panel-subtitle">
|
|
One place for quote truth: every row starts at the incoming quote, then shows whether we responded, why not, and whether any asset movement was proven.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="metric-grid">
|
|
<MetricCard label="Successful trades" meta="Requires linked terminal outcome and settlement" value={String(funnel.successful_trade_count || 0)} />
|
|
<MetricCard
|
|
label="Gross edge est."
|
|
meta={`${funnel.successful_trade_gross_edge_estimate_count || 0} proven trades, before fees`}
|
|
signedValue={funnel.successful_trade_gross_edge_estimate_eure}
|
|
value={formatEur(funnel.successful_trade_gross_edge_estimate_eure)}
|
|
/>
|
|
<MetricCard label="Not filled" meta="Submitted but no settled inventory delta" value={String(counts.not_filled || 0)} />
|
|
<MetricCard label="Awaiting outcome" meta="Submitted, no durable terminal result yet" value={String(funnel.unresolved_submission_count || 0)} />
|
|
<MetricCard label="Rejected / blocked" meta="Strategy rejection or executor block" value={String((counts.rejected || 0) + (counts.blocked || 0))} />
|
|
<MetricCard label="Strategy armed" meta={`Paused ${formatBoolean(strategy.strategy_state.paused)}`} value={formatBoolean(strategy.strategy_state.armed)} />
|
|
<MetricCard label="Executor armed" meta={`Paused ${formatBoolean(strategy.executor_state.paused)}`} value={formatBoolean(strategy.executor_state.armed)} />
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function SuccessfulTradesSection({ funnel, counts }) {
|
|
return (
|
|
<section className="panel">
|
|
<div className="panel-head">
|
|
<div>
|
|
<div className="eyebrow">Successful trades only</div>
|
|
<h3>Trades with proven asset movement</h3>
|
|
<div className="panel-subtitle">
|
|
This table excludes submitted-only quote responses. Realized PnL remains unavailable until fees and venue-native terminal fills are stored.
|
|
</div>
|
|
</div>
|
|
<div className="pills">
|
|
<Pill label={`${counts.completed || 0} completed`} stateLabel={(counts.completed || 0) > 0 ? 'healthy' : 'unknown'} />
|
|
<Pill label={`${formatEur(funnel.successful_trade_gross_edge_estimate_eure)} gross edge est.`} stateLabel={funnel.successful_trade_gross_edge_estimate_eure ? 'healthy' : 'unknown'} />
|
|
<Pill label={`${counts.not_filled || 0} not filled`} stateLabel={(counts.not_filled || 0) > 0 ? 'warning' : 'unknown'} />
|
|
</div>
|
|
</div>
|
|
<SuccessfulTradesTable items={funnel.successful_trades} />
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function QuoteLifecycleSection({ items }) {
|
|
return (
|
|
<section className="panel full-width-evidence-panel">
|
|
<div className="panel-head">
|
|
<div>
|
|
<div className="eyebrow">Quote lifecycle</div>
|
|
<h3>Incoming quotes and what happened next</h3>
|
|
<div className="panel-subtitle">
|
|
Full-width quote table: incoming quote, response decision, result, decisive reason, and expandable lifecycle stages.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<QuoteLifecycleTable items={items} />
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export default function StrategyPage({ strategy, onControl, page = 'strategy' }) {
|
|
const funnel = strategy.strategy_state.trade_funnel || {};
|
|
const counts = funnel.counts || {};
|
|
|
|
switch (page) {
|
|
case 'strategy-pairs':
|
|
return <PairConfigSection assetCatalog={strategy.asset_catalog} pairConfig={strategy.pair_config} onControl={onControl} />;
|
|
case 'strategy-competitiveness':
|
|
return (
|
|
<MakerCompetitivenessSection
|
|
pairConfig={strategy.pair_config}
|
|
summary={strategy.strategy_state.maker_competitiveness}
|
|
/>
|
|
);
|
|
case 'strategy-assets':
|
|
return <AssetCatalogSection assetCatalog={strategy.asset_catalog} onControl={onControl} />;
|
|
case 'strategy-trades':
|
|
return <SuccessfulTradesSection counts={counts} funnel={funnel} />;
|
|
case 'strategy-lifecycle':
|
|
return <QuoteLifecycleSection items={strategy.strategy_state.recent_lifecycle_rows} />;
|
|
case 'strategy':
|
|
default:
|
|
return <StrategyOverviewSection strategy={strategy} />;
|
|
}
|
|
}
|