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_version:
|
||||
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',
|
||||
request_kind: row.request_kind || decision.request_kind || row.command?.request_kind || 'unknown',
|
||||
result_code: resultCode || 'no_result',
|
||||
|
|
@ -154,6 +155,7 @@ function groupEntries(entries) {
|
|||
entry.pair || 'unknown',
|
||||
entry.direction || 'unknown',
|
||||
entry.request_kind || 'unknown',
|
||||
entry.edge_bps ?? 'unknown',
|
||||
entry.result_code || 'no_result',
|
||||
entry.failure_category || 'none',
|
||||
entry.quote_age_bucket,
|
||||
|
|
@ -173,6 +175,7 @@ function summarizeGroup(entries) {
|
|||
pair: first.pair || null,
|
||||
direction: first.direction || 'unknown',
|
||||
request_kind: first.request_kind || 'unknown',
|
||||
edge_bps: first.edge_bps ?? null,
|
||||
result_code: first.result_code || 'no_result',
|
||||
failure_category: first.failure_category || null,
|
||||
quote_age_bucket: first.quote_age_bucket,
|
||||
|
|
|
|||
|
|
@ -1248,6 +1248,7 @@ export function deriveQuoteLifecycleRows({
|
|||
pair: outcome?.pair || null,
|
||||
direction: outcome?.direction || null,
|
||||
request_kind: outcome?.request_kind || null,
|
||||
edge_bps: outcome?.edge_bps || null,
|
||||
gross_edge_pct: outcome?.gross_edge_pct || null,
|
||||
notional: outcome?.notional || null,
|
||||
notional_asset_id: outcome?.notional_asset_id || null,
|
||||
|
|
|
|||
|
|
@ -311,6 +311,7 @@ function baseOutcomeRecord({
|
|||
pair: command?.pair || decision?.pair || submission.pair || null,
|
||||
direction: decision?.direction || command?.direction || 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,
|
||||
notional: decision?.notional || command?.notional || null,
|
||||
notional_asset_id: decision?.notional_asset_id || command?.notional_asset_id || null,
|
||||
|
|
@ -443,6 +444,7 @@ function normalizeCommand(entry) {
|
|||
pair: payload.pair || null,
|
||||
direction: payload.direction || null,
|
||||
request_kind: payload.request_kind || null,
|
||||
edge_bps: payload.edge_bps || null,
|
||||
notional: payload.notional || null,
|
||||
notional_asset_id: payload.notional_asset_id || null,
|
||||
notional_symbol: payload.notional_symbol || null,
|
||||
|
|
@ -476,6 +478,7 @@ function normalizeDecision(entry) {
|
|||
pair: payload.pair || null,
|
||||
direction: payload.direction || null,
|
||||
request_kind: payload.request_kind || null,
|
||||
edge_bps: payload.edge_bps || null,
|
||||
gross_edge_pct: payload.gross_edge_pct || null,
|
||||
notional: payload.notional || null,
|
||||
notional_asset_id: payload.notional_asset_id || null,
|
||||
|
|
|
|||
|
|
@ -3390,6 +3390,7 @@ function normalizeQuoteOutcomeRow(row) {
|
|||
pair: payload.pair || null,
|
||||
direction: payload.direction || null,
|
||||
request_kind: payload.request_kind || null,
|
||||
edge_bps: payload.edge_bps || null,
|
||||
gross_edge_pct: payload.gross_edge_pct || null,
|
||||
notional: payload.notional || null,
|
||||
notional_asset_id: payload.notional_asset_id || null,
|
||||
|
|
|
|||
|
|
@ -293,6 +293,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
|||
<th>Pair</th>
|
||||
<th>Direction</th>
|
||||
<th>Request</th>
|
||||
<th>Edge</th>
|
||||
<th>Result</th>
|
||||
<th>Age / notional</th>
|
||||
<th>Outcome</th>
|
||||
|
|
@ -304,13 +305,14 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
|||
{groups.length ? groups.slice(0, 12).map((group, index) => {
|
||||
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.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>
|
||||
<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}
|
||||
|
|
@ -331,7 +333,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
|||
</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>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ test('maker competitiveness aggregates pair, direction, request kind, result, fa
|
|||
pair: `${nbtc}->${eure}`,
|
||||
direction: 'base_to_quote',
|
||||
request_kind: 'exact_in',
|
||||
edge_bps: '49',
|
||||
notional: '5',
|
||||
notional_symbol: 'EURe',
|
||||
outcome_status: 'submitted',
|
||||
|
|
@ -98,6 +99,7 @@ test('maker competitiveness aggregates pair, direction, request kind, result, fa
|
|||
pair: `${nbtc}->${usdc}`,
|
||||
direction: 'base_to_quote',
|
||||
request_kind: 'exact_in',
|
||||
edge_bps: '20',
|
||||
notional: '8',
|
||||
notional_symbol: 'USDC',
|
||||
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}`,
|
||||
direction: 'base_to_quote',
|
||||
request_kind: 'exact_in',
|
||||
edge_bps: '20',
|
||||
notional: '12',
|
||||
notional_symbol: 'USDC',
|
||||
maker_timing: extendMakerTiming(baseTiming, {
|
||||
|
|
@ -145,6 +148,7 @@ test('maker competitiveness aggregates pair, direction, request kind, result, fa
|
|||
assert.ok(summary.groups.some((group) => (
|
||||
group.pair === `${nbtc}->${usdc}`
|
||||
&& group.request_kind === 'exact_in'
|
||||
&& group.edge_bps === '20'
|
||||
&& group.result_code === 'submission_failed'
|
||||
&& group.failure_category === 'quote_not_found_or_finished'
|
||||
&& 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.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, /maker_competitiveness/);
|
||||
assert.match(strategySource, /pairDisplayLabel/);
|
||||
assert.match(strategySource, /group\.edge_bps/);
|
||||
assert.match(stylesSource, /\.two-column-grid/);
|
||||
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',
|
||||
decision_id: 'decision-1',
|
||||
quote_id: 'quote-1',
|
||||
edge_bps: '10',
|
||||
min_deadline_ms: '15000',
|
||||
asset_in: eureAsset.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[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);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue