diff --git a/src/core/operator-dashboard.mjs b/src/core/operator-dashboard.mjs
index b09ace2..33fb751 100644
--- a/src/core/operator-dashboard.mjs
+++ b/src/core/operator-dashboard.mjs
@@ -1341,8 +1341,12 @@ function buildTradeFunnelSummary(lifecycleRows = []) {
}
}
+ const grossEdgeEstimate = summarizeGrossEdgeEstimate(successfulTrades);
+
return {
successful_trade_count: successfulTrades.length,
+ successful_trade_gross_edge_estimate_eure: grossEdgeEstimate.total_eure,
+ successful_trade_gross_edge_estimate_count: grossEdgeEstimate.count,
unresolved_submission_count: unresolvedSubmissions.length,
no_trade_count: noTradeRows.length,
successful_trades: successfulTrades,
@@ -1356,6 +1360,23 @@ function buildTradeFunnelSummary(lifecycleRows = []) {
};
}
+function summarizeGrossEdgeEstimate(rows = []) {
+ let total = 0n;
+ let count = 0;
+
+ for (const row of rows || []) {
+ const value = row?.gross_edge_value_eure || estimateGrossEdgeValueEure(row);
+ if (value == null) continue;
+ total += parseScaledDecimal(value);
+ count += 1;
+ }
+
+ return {
+ count,
+ total_eure: count > 0 ? formatScaledDecimal(total) : null,
+ };
+}
+
function buildSystemSummary({ servicesByName, activeAlerts, recentAlerts }) {
const historyWriterState = servicesByName['history-writer']?.state || {};
void activeAlerts;
diff --git a/src/operator-dashboard/static/pages/StrategyPage.jsx b/src/operator-dashboard/static/pages/StrategyPage.jsx
index b9a82be..005348d 100644
--- a/src/operator-dashboard/static/pages/StrategyPage.jsx
+++ b/src/operator-dashboard/static/pages/StrategyPage.jsx
@@ -293,6 +293,12 @@ export default function StrategyPage({ strategy }) {
+
@@ -312,6 +318,7 @@ export default function StrategyPage({ strategy }) {
0 ? 'healthy' : 'unknown'} />
+
0 ? 'warning' : 'unknown'} />
diff --git a/test/operator-dashboard-ui-static.test.mjs b/test/operator-dashboard-ui-static.test.mjs
index 06b0b25..c0beb47 100644
--- a/test/operator-dashboard-ui-static.test.mjs
+++ b/test/operator-dashboard-ui-static.test.mjs
@@ -11,6 +11,9 @@ test('strategy page owns consolidated quote lifecycle and successful trade table
assert.match(strategySource, /Incoming quotes and what happened next/);
assert.match(strategySource, /Responded\?/);
assert.match(strategySource, /Successful trades only/);
+ assert.match(strategySource, /Gross edge est\./);
+ assert.match(strategySource, /successful_trade_gross_edge_estimate_eure/);
+ assert.match(strategySource, /before fees/);
assert.match(strategySource, /Show lifecycle/);
assert.match(strategySource, /Submitted means the relay accepted the response; it does not prove a trade\./);
assert.doesNotMatch(strategySource, /Actionable|actionable/);
diff --git a/test/operator-dashboard.test.mjs b/test/operator-dashboard.test.mjs
index 8838806..ef0aa44 100644
--- a/test/operator-dashboard.test.mjs
+++ b/test/operator-dashboard.test.mjs
@@ -752,6 +752,8 @@ test('successful trade rows require completed outcome with linked settled invent
command_id: 'cmd-completed',
quote_id: 'quote-completed',
pair: config.activePair,
+ gross_edge_pct: '0.49',
+ eure_notional: '50',
outcome_status: 'completed',
outcome_reason: 'matched_inventory_delta',
outcome_observed_at: '2026-04-09T09:01:00.000Z',
@@ -770,6 +772,8 @@ test('successful trade rows require completed outcome with linked settled invent
command_id: 'cmd-completed-no-delta',
quote_id: 'quote-completed-no-delta',
pair: config.activePair,
+ gross_edge_pct: '99',
+ eure_notional: '100',
outcome_status: 'completed',
outcome_reason: 'settled',
outcome_observed_at: '2026-04-09T09:02:00.000Z',
@@ -786,6 +790,8 @@ test('successful trade rows require completed outcome with linked settled invent
const funnel = bootstrap.strategy.strategy_state.trade_funnel;
assert.equal(funnel.successful_trade_count, 1);
assert.equal(funnel.successful_trades[0].quote_id, 'quote-completed');
+ assert.equal(funnel.successful_trade_gross_edge_estimate_eure, '0.245');
+ assert.equal(funnel.successful_trade_gross_edge_estimate_count, 1);
assert.match(funnel.successful_trades[0].settlement_summary.text, /\+0\.00037014 BTC/);
assert.equal(funnel.counts.submitted, 1);
assert.equal(funnel.counts.completed, 2);