Expose maker edge competitiveness
All checks were successful
deploy / deploy (push) Successful in 48s
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:
parent
365acf7b7f
commit
1d66ae208f
8 changed files with 47 additions and 2 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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']);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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/);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue