Proof: quote lifecycle rows now anchor row time and sorting to quote/submission activity instead of later outcome recomputation; regression covers an old 2% not-filled outcome recomputed after a current 0.49% quote. Assumptions: outcome-only historical rows still belong in lifecycle evidence, but their dashboard recency must come from original command/submission time when quote evidence is not loaded. Still fake: venue-native terminal fill events and fee-complete realized PnL remain unavailable.
This commit is contained in:
parent
a4a60fd521
commit
558a162cd2
3 changed files with 67 additions and 3 deletions
|
|
@ -961,13 +961,15 @@ export function deriveQuoteLifecycleRows({
|
|||
gross_edge_pct: outcome?.gross_edge_pct || null,
|
||||
eure_notional: outcome?.eure_notional || null,
|
||||
outcome,
|
||||
command_at: outcome?.command_at || null,
|
||||
execution_result_at: outcome?.submitted_at || null,
|
||||
outcome_observed_at: outcome?.outcome_observed_at || outcome?.submitted_at || null,
|
||||
});
|
||||
}
|
||||
|
||||
const finalized = [...rowsByKey.values()]
|
||||
.map((row) => finalizeLifecycleRow(row))
|
||||
.sort((left, right) => sortTimestamps(right.latest_stage_at, left.latest_stage_at));
|
||||
.sort((left, right) => sortTimestamps(right.quote_activity_at, left.quote_activity_at));
|
||||
|
||||
return limit == null ? finalized : finalized.slice(0, limit);
|
||||
}
|
||||
|
|
@ -1101,6 +1103,13 @@ function finalizeLifecycleRow(row) {
|
|||
|| row.decision_at
|
||||
|| row.quote_observed_at
|
||||
|| null,
|
||||
quote_activity_at:
|
||||
row.quote_observed_at
|
||||
|| row.decision_at
|
||||
|| row.command_at
|
||||
|| row.execution_result_at
|
||||
|| row.outcome_observed_at
|
||||
|| null,
|
||||
stage_details: {
|
||||
quote_observed_at: row.quote_observed_at,
|
||||
decision_at: row.decision_at,
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ function QuoteLifecycleTable({ items }) {
|
|||
<table className="quote-lifecycle-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Quote time</th>
|
||||
<th>Quote id</th>
|
||||
<th>Request</th>
|
||||
<th>Responded?</th>
|
||||
|
|
@ -156,7 +156,12 @@ function QuoteLifecycleTable({ items }) {
|
|||
return (
|
||||
<Fragment key={rowKey}>
|
||||
<tr key={`${rowKey}:row`}>
|
||||
<td>{formatTimestamp(item.latest_stage_at)}</td>
|
||||
<td>
|
||||
<div>{formatTimestamp(item.quote_activity_at || item.latest_stage_at)}</div>
|
||||
{item.latest_stage_at && item.latest_stage_at !== item.quote_activity_at ? (
|
||||
<div className="status-subtle">Updated {formatTimestamp(item.latest_stage_at)}</div>
|
||||
) : null}
|
||||
</td>
|
||||
<td><IdentifierRow label="Quote" value={item.quote_id} /></td>
|
||||
<td>
|
||||
<div>{formatTerms(item.request_terms || item.submitted_terms)}</div>
|
||||
|
|
|
|||
|
|
@ -670,6 +670,56 @@ test('submitted lifecycle evidence never becomes completed by itself', () => {
|
|||
assert.doesNotMatch(`${submitted[0].lifecycle_label} ${submitted[0].reason_text}`, /completed|successful trade|asset delta/i);
|
||||
});
|
||||
|
||||
test('quote lifecycle recency is anchored to quote or submission time, not later outcome recompute', () => {
|
||||
const rows = deriveQuoteLifecycleRows({
|
||||
recentTradeDecisions: [{
|
||||
observed_at: '2026-04-13T22:33:18.000Z',
|
||||
payload: {
|
||||
decision_id: 'decision-current',
|
||||
quote_id: 'quote-current',
|
||||
pair: 'btc->eure',
|
||||
decision: 'actionable',
|
||||
decision_reason: 'actionable',
|
||||
gross_edge_pct: '0.490000',
|
||||
},
|
||||
}],
|
||||
recentExecutionResults: [{
|
||||
command_id: 'cmd-current',
|
||||
decision_id: 'decision-current',
|
||||
quote_id: 'quote-current',
|
||||
pair: 'btc->eure',
|
||||
result_at: '2026-04-13T22:33:19.000Z',
|
||||
status: 'submitted',
|
||||
result_code: 'quote_response_ok',
|
||||
}],
|
||||
recentQuoteOutcomes: [{
|
||||
command_id: 'cmd-old',
|
||||
decision_id: 'decision-old',
|
||||
quote_id: 'quote-old-two-percent',
|
||||
pair: 'btc->eure',
|
||||
gross_edge_pct: '2.000000',
|
||||
eure_notional: '75.58',
|
||||
submitted_at: '2026-04-09T23:36:35.566Z',
|
||||
command_at: '2026-04-09T23:36:35.352Z',
|
||||
outcome_status: 'not_filled',
|
||||
outcome_reason: 'deadline_elapsed_without_settlement',
|
||||
outcome_observed_at: '2026-04-13T22:35:33.295Z',
|
||||
outcome_source: 'submission_deadline_and_inventory_snapshots',
|
||||
attribution_status: 'unattributed',
|
||||
attributed_inventory_delta: null,
|
||||
}],
|
||||
limit: null,
|
||||
});
|
||||
|
||||
assert.equal(rows[0].quote_id, 'quote-current');
|
||||
assert.equal(rows[0].gross_edge_pct, '0.490000');
|
||||
|
||||
const oldRow = rows.find((row) => row.quote_id === 'quote-old-two-percent');
|
||||
assert.equal(oldRow.quote_activity_at, '2026-04-09T23:36:35.352Z');
|
||||
assert.equal(oldRow.latest_stage_at, '2026-04-13T22:35:33.295Z');
|
||||
assert.equal(oldRow.lifecycle_state, 'not_filled');
|
||||
});
|
||||
|
||||
test('successful trade rows require completed outcome with linked settled inventory evidence', () => {
|
||||
const config = buildConfig();
|
||||
const bootstrap = buildDashboardBootstrap({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue