diff --git a/src/operator-dashboard/static/pages/StrategyPage.jsx b/src/operator-dashboard/static/pages/StrategyPage.jsx index efe7cb4..c59c05f 100644 --- a/src/operator-dashboard/static/pages/StrategyPage.jsx +++ b/src/operator-dashboard/static/pages/StrategyPage.jsx @@ -115,6 +115,18 @@ function grossEdgeEstimate(item) { 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) { if (item.notional_display) return item.notional_display; if (item.notional != null) return `${item.notional}${item.notional_symbol ? ` ${item.notional_symbol}` : ''}`; @@ -212,7 +224,8 @@ function LifecycleDetails({ item }) {
{plainCodeLabel(item.decision?.decision_reason || item.reason_code, 'No decision reason recorded')}
-
{item.gross_edge_pct ? `Edge ${item.gross_edge_pct}%` : 'Edge unavailable'}
+
{formatGrossEdgePct(item.gross_edge_pct)}
+
{formatConfiguredEdgeBps(item.edge_bps)}
{notionalLabel(item)}
@@ -532,7 +545,7 @@ function QuoteLifecycleTable({ items }) { Responded? Result Reason - Edge / notional + Gross edge / notional Lifecycle @@ -563,7 +576,8 @@ function QuoteLifecycleTable({ items }) {
{item.reason_code || 'reason_unknown'}
-
0 ? 'value-positive' : Number(item.gross_edge_pct) < 0 ? 'value-negative' : ''}>{item.gross_edge_pct ? `${item.gross_edge_pct}%` : 'Unavailable'}
+
0 ? 'value-positive' : Number(item.gross_edge_pct) < 0 ? 'value-negative' : ''}>{formatGrossEdgePct(item.gross_edge_pct)}
+
{formatConfiguredEdgeBps(item.edge_bps)}
{notionalLabel(item)}
@@ -614,7 +628,7 @@ function SuccessfulTradesTable({ items }) { Completed Quote id - Edge + Gross edge Gross edge est. Settlement Realized PnL @@ -631,7 +645,8 @@ function SuccessfulTradesTable({ items }) { {formatTimestamp(item.latest_stage_at)} -
{item.gross_edge_pct ? `${item.gross_edge_pct}%` : 'Unavailable'}
+
{formatGrossEdgePct(item.gross_edge_pct)}
+
{formatConfiguredEdgeBps(item.edge_bps)}
{notionalLabel(item)}
@@ -998,7 +1013,7 @@ function PairConfigSection({ assetCatalog, pairConfig, onControl }) { Pair Mode - Edge + Configured edge Limits Route Blocked @@ -1025,7 +1040,7 @@ function PairConfigSection({ assetCatalog, pairConfig, onControl }) {
{truncateMiddle(pairId, 42)}
- {strategyConfig.edge_bps ?? 'Unavailable'} bps + {formatConfiguredEdgeBps(strategyConfig.edge_bps, { prefix: false })}
{strategyConfig.max_notional || 'Unavailable'} max
diff --git a/test/operator-dashboard-ui-static.test.mjs b/test/operator-dashboard-ui-static.test.mjs index 8c47bd3..d823e52 100644 --- a/test/operator-dashboard-ui-static.test.mjs +++ b/test/operator-dashboard-ui-static.test.mjs @@ -132,6 +132,14 @@ test('strategy page exposes maker timing waterfall and competitiveness summaries 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', () => { assert.ok( strategySource.indexOf('