Clarify dashboard edge units
All checks were successful
deploy / deploy (push) Successful in 1m0s

Proof: operator dashboard now labels configured strategy edge as bps plus percent and labels quote opportunity edge as gross edge percent, preventing configured edge_bps=20 from being read as 20%.

Assumptions: this is a display-only fix; strategy decisions, relay submissions, pair enablement, edge thresholds, notional limits, inventory, arming, and response policy are unchanged.

Still fake: venue-native terminal fill ids and fee-complete realized PnL remain unavailable.
This commit is contained in:
philipp 2026-05-19 16:30:41 +02:00
parent b6646fb7a3
commit 748950a1d8
2 changed files with 30 additions and 7 deletions

View file

@ -115,6 +115,18 @@ function grossEdgeEstimate(item) {
return symbol ? `${item.gross_edge_value} ${symbol}` : 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) { function notionalLabel(item) {
if (item.notional_display) return item.notional_display; if (item.notional_display) return item.notional_display;
if (item.notional != null) return `${item.notional}${item.notional_symbol ? ` ${item.notional_symbol}` : ''}`; if (item.notional != null) return `${item.notional}${item.notional_symbol ? ` ${item.notional_symbol}` : ''}`;
@ -212,7 +224,8 @@ function LifecycleDetails({ item }) {
<StageCard at={item.decision_at} status={strategyDecisionStatus(item.decision)} title="2. Strategy decided"> <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>{plainCodeLabel(item.decision?.decision_reason || item.reason_code, 'No decision reason recorded')}</div>
<div className="status-subtle">{item.gross_edge_pct ? `Edge ${item.gross_edge_pct}%` : 'Edge unavailable'}</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> <div className="status-subtle">{notionalLabel(item)}</div>
</StageCard> </StageCard>
@ -532,7 +545,7 @@ function QuoteLifecycleTable({ items }) {
<th>Responded?</th> <th>Responded?</th>
<th>Result</th> <th>Result</th>
<th>Reason</th> <th>Reason</th>
<th>Edge / notional</th> <th>Gross edge / notional</th>
<th>Lifecycle</th> <th>Lifecycle</th>
</tr> </tr>
</thead> </thead>
@ -563,7 +576,8 @@ function QuoteLifecycleTable({ items }) {
<div className="status-subtle mono">{item.reason_code || 'reason_unknown'}</div> <div className="status-subtle mono">{item.reason_code || 'reason_unknown'}</div>
</td> </td>
<td> <td>
<div className={Number(item.gross_edge_pct) > 0 ? 'value-positive' : Number(item.gross_edge_pct) < 0 ? 'value-negative' : ''}>{item.gross_edge_pct ? `${item.gross_edge_pct}%` : 'Unavailable'}</div> <div className={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">{formatConfiguredEdgeBps(item.edge_bps)}</div>
<div className="status-subtle">{notionalLabel(item)}</div> <div className="status-subtle">{notionalLabel(item)}</div>
</td> </td>
<td> <td>
@ -614,7 +628,7 @@ function SuccessfulTradesTable({ items }) {
<tr> <tr>
<th>Completed</th> <th>Completed</th>
<th>Quote id</th> <th>Quote id</th>
<th>Edge</th> <th>Gross edge</th>
<th>Gross edge est.</th> <th>Gross edge est.</th>
<th>Settlement</th> <th>Settlement</th>
<th>Realized PnL</th> <th>Realized PnL</th>
@ -631,7 +645,8 @@ function SuccessfulTradesTable({ items }) {
<td>{formatTimestamp(item.latest_stage_at)}</td> <td>{formatTimestamp(item.latest_stage_at)}</td>
<td><IdentifierRow label="Quote" value={item.quote_id} /></td> <td><IdentifierRow label="Quote" value={item.quote_id} /></td>
<td> <td>
<div>{item.gross_edge_pct ? `${item.gross_edge_pct}%` : 'Unavailable'}</div> <div>{formatGrossEdgePct(item.gross_edge_pct)}</div>
<div className="status-subtle">{formatConfiguredEdgeBps(item.edge_bps)}</div>
<div className="status-subtle">{notionalLabel(item)}</div> <div className="status-subtle">{notionalLabel(item)}</div>
</td> </td>
<td> <td>
@ -998,7 +1013,7 @@ function PairConfigSection({ assetCatalog, pairConfig, onControl }) {
<tr> <tr>
<th>Pair</th> <th>Pair</th>
<th>Mode</th> <th>Mode</th>
<th>Edge</th> <th>Configured edge</th>
<th>Limits</th> <th>Limits</th>
<th>Route</th> <th>Route</th>
<th>Blocked</th> <th>Blocked</th>
@ -1025,7 +1040,7 @@ function PairConfigSection({ assetCatalog, pairConfig, onControl }) {
<div className="status-subtle mono">{truncateMiddle(pairId, 42)}</div> <div className="status-subtle mono">{truncateMiddle(pairId, 42)}</div>
</td> </td>
<td><Pill label={pair.mode || pair.status} stateLabel={pair.canTrade || pair.can_trade ? 'healthy' : 'warning'} /></td> <td><Pill label={pair.mode || pair.status} stateLabel={pair.canTrade || pair.can_trade ? 'healthy' : 'warning'} /></td>
<td>{strategyConfig.edge_bps ?? 'Unavailable'} bps</td> <td>{formatConfiguredEdgeBps(strategyConfig.edge_bps, { prefix: false })}</td>
<td> <td>
<div>{strategyConfig.max_notional || 'Unavailable'} max</div> <div>{strategyConfig.max_notional || 'Unavailable'} max</div>
<div className="status-subtle"> <div className="status-subtle">

View file

@ -132,6 +132,14 @@ test('strategy page exposes maker timing waterfall and competitiveness summaries
assert.match(stylesSource, /\.timing-waterfall-table/); assert.match(stylesSource, /\.timing-waterfall-table/);
}); });
test('strategy page distinguishes configured bps from gross quote edge percent', () => {
assert.match(strategySource, /formatConfiguredEdgeBps/);
assert.match(strategySource, /Configured edge/);
assert.match(strategySource, /Gross edge/);
assert.match(strategySource, /number \/ 100/);
assert.doesNotMatch(strategySource, /Edge \$\{item\.edge_bps\}%/);
});
test('pair controls are rendered before the long asset catalog table', () => { test('pair controls are rendered before the long asset catalog table', () => {
assert.ok( assert.ok(
strategySource.indexOf('<PairConfigSection') < strategySource.indexOf('<AssetCatalogSection'), strategySource.indexOf('<PairConfigSection') < strategySource.indexOf('<AssetCatalogSection'),