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(' |