Expose maker edge competitiveness
All checks were successful
deploy / deploy (push) Successful in 48s

Proof: Maker competitiveness now persists edge_bps into quote outcome payloads, groups summaries by edge, and shows the edge in the operator dashboard so filled versus not-filled responses can be compared against configured strategy edge.

Assumptions: Edge bps remains DB-owned pair strategy config; this change is observational and does not change live pair enablement, notional limits, inventory checks, response policy, or relay submission behavior.

Still fake: Venue-native terminal fill ids and fee-complete realized PnL remain unavailable; relay acceptance is still only submission evidence.
This commit is contained in:
philipp 2026-05-19 15:45:30 +02:00
parent 365acf7b7f
commit 1d66ae208f
8 changed files with 47 additions and 2 deletions

View file

@ -103,6 +103,7 @@ export function normalizeCompetitivenessEntry(row = {}) {
pair_config_id: row.pair_config_id || decision.pair_config_id || execution.pair_config_id || null, pair_config_id: row.pair_config_id || decision.pair_config_id || execution.pair_config_id || null,
pair_config_version: pair_config_version:
row.pair_config_version || decision.pair_config_version || execution.pair_config_version || null, row.pair_config_version || decision.pair_config_version || execution.pair_config_version || null,
edge_bps: row.edge_bps ?? decision.edge_bps ?? row.command?.edge_bps ?? execution.edge_bps ?? null,
direction: row.direction || decision.direction || row.command?.direction || 'unknown', direction: row.direction || decision.direction || row.command?.direction || 'unknown',
request_kind: row.request_kind || decision.request_kind || row.command?.request_kind || 'unknown', request_kind: row.request_kind || decision.request_kind || row.command?.request_kind || 'unknown',
result_code: resultCode || 'no_result', result_code: resultCode || 'no_result',
@ -154,6 +155,7 @@ function groupEntries(entries) {
entry.pair || 'unknown', entry.pair || 'unknown',
entry.direction || 'unknown', entry.direction || 'unknown',
entry.request_kind || 'unknown', entry.request_kind || 'unknown',
entry.edge_bps ?? 'unknown',
entry.result_code || 'no_result', entry.result_code || 'no_result',
entry.failure_category || 'none', entry.failure_category || 'none',
entry.quote_age_bucket, entry.quote_age_bucket,
@ -173,6 +175,7 @@ function summarizeGroup(entries) {
pair: first.pair || null, pair: first.pair || null,
direction: first.direction || 'unknown', direction: first.direction || 'unknown',
request_kind: first.request_kind || 'unknown', request_kind: first.request_kind || 'unknown',
edge_bps: first.edge_bps ?? null,
result_code: first.result_code || 'no_result', result_code: first.result_code || 'no_result',
failure_category: first.failure_category || null, failure_category: first.failure_category || null,
quote_age_bucket: first.quote_age_bucket, quote_age_bucket: first.quote_age_bucket,

View file

@ -1248,6 +1248,7 @@ export function deriveQuoteLifecycleRows({
pair: outcome?.pair || null, pair: outcome?.pair || null,
direction: outcome?.direction || null, direction: outcome?.direction || null,
request_kind: outcome?.request_kind || null, request_kind: outcome?.request_kind || null,
edge_bps: outcome?.edge_bps || null,
gross_edge_pct: outcome?.gross_edge_pct || null, gross_edge_pct: outcome?.gross_edge_pct || null,
notional: outcome?.notional || null, notional: outcome?.notional || null,
notional_asset_id: outcome?.notional_asset_id || null, notional_asset_id: outcome?.notional_asset_id || null,

View file

@ -311,6 +311,7 @@ function baseOutcomeRecord({
pair: command?.pair || decision?.pair || submission.pair || null, pair: command?.pair || decision?.pair || submission.pair || null,
direction: decision?.direction || command?.direction || null, direction: decision?.direction || command?.direction || null,
request_kind: command?.request_kind || decision?.request_kind || null, request_kind: command?.request_kind || decision?.request_kind || null,
edge_bps: command?.edge_bps || decision?.edge_bps || submission.edge_bps || null,
gross_edge_pct: decision?.gross_edge_pct || null, gross_edge_pct: decision?.gross_edge_pct || null,
notional: decision?.notional || command?.notional || null, notional: decision?.notional || command?.notional || null,
notional_asset_id: decision?.notional_asset_id || command?.notional_asset_id || null, notional_asset_id: decision?.notional_asset_id || command?.notional_asset_id || null,
@ -443,6 +444,7 @@ function normalizeCommand(entry) {
pair: payload.pair || null, pair: payload.pair || null,
direction: payload.direction || null, direction: payload.direction || null,
request_kind: payload.request_kind || null, request_kind: payload.request_kind || null,
edge_bps: payload.edge_bps || null,
notional: payload.notional || null, notional: payload.notional || null,
notional_asset_id: payload.notional_asset_id || null, notional_asset_id: payload.notional_asset_id || null,
notional_symbol: payload.notional_symbol || null, notional_symbol: payload.notional_symbol || null,
@ -476,6 +478,7 @@ function normalizeDecision(entry) {
pair: payload.pair || null, pair: payload.pair || null,
direction: payload.direction || null, direction: payload.direction || null,
request_kind: payload.request_kind || null, request_kind: payload.request_kind || null,
edge_bps: payload.edge_bps || null,
gross_edge_pct: payload.gross_edge_pct || null, gross_edge_pct: payload.gross_edge_pct || null,
notional: payload.notional || null, notional: payload.notional || null,
notional_asset_id: payload.notional_asset_id || null, notional_asset_id: payload.notional_asset_id || null,

View file

@ -3390,6 +3390,7 @@ function normalizeQuoteOutcomeRow(row) {
pair: payload.pair || null, pair: payload.pair || null,
direction: payload.direction || null, direction: payload.direction || null,
request_kind: payload.request_kind || null, request_kind: payload.request_kind || null,
edge_bps: payload.edge_bps || null,
gross_edge_pct: payload.gross_edge_pct || null, gross_edge_pct: payload.gross_edge_pct || null,
notional: payload.notional || null, notional: payload.notional || null,
notional_asset_id: payload.notional_asset_id || null, notional_asset_id: payload.notional_asset_id || null,

View file

@ -293,6 +293,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
<th>Pair</th> <th>Pair</th>
<th>Direction</th> <th>Direction</th>
<th>Request</th> <th>Request</th>
<th>Edge</th>
<th>Result</th> <th>Result</th>
<th>Age / notional</th> <th>Age / notional</th>
<th>Outcome</th> <th>Outcome</th>
@ -304,13 +305,14 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
{groups.length ? groups.slice(0, 12).map((group, index) => { {groups.length ? groups.slice(0, 12).map((group, index) => {
const quoteToRelay = group.latency_stages?.find((stage) => stage.stage === 'quote_to_relay_result_ms'); const quoteToRelay = group.latency_stages?.find((stage) => stage.stage === 'quote_to_relay_result_ms');
return ( return (
<tr key={`${group.pair}:${group.direction}:${group.request_kind}:${group.result_code}:${group.quote_age_bucket}:${index}`}> <tr key={`${group.pair}:${group.direction}:${group.request_kind}:${group.edge_bps ?? 'edge'}:${group.result_code}:${group.quote_age_bucket}:${index}`}>
<td> <td>
<div>{pairDisplayLabel(group.pair, pairConfig)}</div> <div>{pairDisplayLabel(group.pair, pairConfig)}</div>
<div className="status-subtle mono">{truncateMiddle(group.pair || '', 42)}</div> <div className="status-subtle mono">{truncateMiddle(group.pair || '', 42)}</div>
</td> </td>
<td>{plainCodeLabel(group.direction)}</td> <td>{plainCodeLabel(group.direction)}</td>
<td>{plainCodeLabel(group.request_kind)}</td> <td>{plainCodeLabel(group.request_kind)}</td>
<td>{group.edge_bps == null ? 'Unavailable' : `${group.edge_bps} bps`}</td>
<td> <td>
<div>{plainCodeLabel(group.result_code)}</div> <div>{plainCodeLabel(group.result_code)}</div>
{group.failure_category ? <div className="status-subtle">{plainCodeLabel(group.failure_category)}</div> : null} {group.failure_category ? <div className="status-subtle">{plainCodeLabel(group.failure_category)}</div> : null}
@ -331,7 +333,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
</tr> </tr>
); );
}) : ( }) : (
<tr><td colSpan={8}>No competitiveness rows are available yet.</td></tr> <tr><td colSpan={9}>No competitiveness rows are available yet.</td></tr>
)} )}
</tbody> </tbody>
</table> </table>

View file

@ -77,6 +77,7 @@ test('maker competitiveness aggregates pair, direction, request kind, result, fa
pair: `${nbtc}->${eure}`, pair: `${nbtc}->${eure}`,
direction: 'base_to_quote', direction: 'base_to_quote',
request_kind: 'exact_in', request_kind: 'exact_in',
edge_bps: '49',
notional: '5', notional: '5',
notional_symbol: 'EURe', notional_symbol: 'EURe',
outcome_status: 'submitted', outcome_status: 'submitted',
@ -98,6 +99,7 @@ test('maker competitiveness aggregates pair, direction, request kind, result, fa
pair: `${nbtc}->${usdc}`, pair: `${nbtc}->${usdc}`,
direction: 'base_to_quote', direction: 'base_to_quote',
request_kind: 'exact_in', request_kind: 'exact_in',
edge_bps: '20',
notional: '8', notional: '8',
notional_symbol: 'USDC', notional_symbol: 'USDC',
execution_result_at: '2026-05-18T10:00:00.400Z', execution_result_at: '2026-05-18T10:00:00.400Z',
@ -120,6 +122,7 @@ test('maker competitiveness aggregates pair, direction, request kind, result, fa
pair: `${nbtc}->${usdc}`, pair: `${nbtc}->${usdc}`,
direction: 'base_to_quote', direction: 'base_to_quote',
request_kind: 'exact_in', request_kind: 'exact_in',
edge_bps: '20',
notional: '12', notional: '12',
notional_symbol: 'USDC', notional_symbol: 'USDC',
maker_timing: extendMakerTiming(baseTiming, { maker_timing: extendMakerTiming(baseTiming, {
@ -145,6 +148,7 @@ test('maker competitiveness aggregates pair, direction, request kind, result, fa
assert.ok(summary.groups.some((group) => ( assert.ok(summary.groups.some((group) => (
group.pair === `${nbtc}->${usdc}` group.pair === `${nbtc}->${usdc}`
&& group.request_kind === 'exact_in' && group.request_kind === 'exact_in'
&& group.edge_bps === '20'
&& group.result_code === 'submission_failed' && group.result_code === 'submission_failed'
&& group.failure_category === 'quote_not_found_or_finished' && group.failure_category === 'quote_not_found_or_finished'
&& group.quote_age_bucket === '250-500ms' && group.quote_age_bucket === '250-500ms'
@ -159,3 +163,31 @@ test('maker competitiveness aggregates pair, direction, request kind, result, fa
assert.equal(summary.latest_errors[0].error_message, 'quote not found or already finished'); assert.equal(summary.latest_errors[0].error_message, 'quote not found or already finished');
assert.equal(summary.policy_skips[0].reason_code, 'maker_quote_too_old'); assert.equal(summary.policy_skips[0].reason_code, 'maker_quote_too_old');
}); });
test('maker competitiveness keeps edge bps as a grouping dimension', () => {
const nbtc = 'nep141:nbtc.bridge.near';
const usdc = 'nep141:usdc.omft.near';
const base = {
pair: `${nbtc}->${usdc}`,
direction: 'base_to_quote',
request_kind: 'exact_in',
notional: '8',
notional_symbol: 'USDC',
outcome_status: 'not_filled',
execution: {
status: 'submitted',
result_code: 'quote_response_ok',
},
};
const summary = buildMakerCompetitivenessSummary({
lifecycleRows: [
{ ...base, quote_id: 'quote-edge-1', edge_bps: '1' },
{ ...base, quote_id: 'quote-edge-20', edge_bps: '20' },
],
});
const edgeGroups = summary.groups.filter((group) => group.pair === `${nbtc}->${usdc}`);
assert.equal(edgeGroups.length, 2);
assert.deepEqual(edgeGroups.map((group) => group.edge_bps).sort(), ['1', '20']);
});

View file

@ -127,6 +127,7 @@ test('strategy page exposes maker timing waterfall and competitiveness summaries
assert.match(strategySource, /quote_not_found_or_finished/); assert.match(strategySource, /quote_not_found_or_finished/);
assert.match(strategySource, /maker_competitiveness/); assert.match(strategySource, /maker_competitiveness/);
assert.match(strategySource, /pairDisplayLabel/); assert.match(strategySource, /pairDisplayLabel/);
assert.match(strategySource, /group\.edge_bps/);
assert.match(stylesSource, /\.two-column-grid/); assert.match(stylesSource, /\.two-column-grid/);
assert.match(stylesSource, /\.timing-waterfall-table/); assert.match(stylesSource, /\.timing-waterfall-table/);
}); });

View file

@ -48,6 +48,7 @@ test('quote outcome refresh bounds source queries and joins by recent quote ids'
command_id: 'cmd-1', command_id: 'cmd-1',
decision_id: 'decision-1', decision_id: 'decision-1',
quote_id: 'quote-1', quote_id: 'quote-1',
edge_bps: '10',
min_deadline_ms: '15000', min_deadline_ms: '15000',
asset_in: eureAsset.assetId, asset_in: eureAsset.assetId,
asset_out: btcAsset.assetId, asset_out: btcAsset.assetId,
@ -124,6 +125,7 @@ test('quote outcome refresh bounds source queries and joins by recent quote ids'
assert.equal(records.length, 1); assert.equal(records.length, 1);
assert.equal(records[0].quote_id, 'quote-1'); assert.equal(records[0].quote_id, 'quote-1');
assert.equal(records[0].payload.edge_bps, '10');
assert.equal(queries.filter((entry) => entry.sql.includes('INSERT INTO quote_outcome_attributions')).length, 1); assert.equal(queries.filter((entry) => entry.sql.includes('INSERT INTO quote_outcome_attributions')).length, 1);
}); });