Split strategy dashboard sections
All checks were successful
deploy / deploy (push) Successful in 58s
All checks were successful
deploy / deploy (push) Successful in 58s
Proof: operator dashboard Strategy panels now render as separate sidebar menu destinations for overview, pair config, competitiveness, asset registry, successful trades, and quote lifecycle; static UI coverage and the dashboard bundle verify the routing. Assumptions: this is a UI-only operator cleanup inside the active maker timing and competitiveness turn; it does not change strategy decisions, pair config, execution, persistence, live funds, or response policy. Still fake: venue-native terminal fill ids and fee-complete realized PnL remain unavailable.
This commit is contained in:
parent
04c59ee93d
commit
686b922342
4 changed files with 143 additions and 74 deletions
|
|
@ -31,6 +31,7 @@ function LoadingPanel({ error, onRetry }) {
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [state, dispatch] = useReducer(dashboardReducer, initialDashboardState);
|
const [state, dispatch] = useReducer(dashboardReducer, initialDashboardState);
|
||||||
const currentPage = state.page || state.dashboard?.default_page || 'funds';
|
const currentPage = state.page || state.dashboard?.default_page || 'funds';
|
||||||
|
const isStrategyPage = currentPage === 'strategy' || currentPage.startsWith('strategy-');
|
||||||
const isReadyForSocket = Boolean(state.session && state.dashboard);
|
const isReadyForSocket = Boolean(state.session && state.dashboard);
|
||||||
const criticalBanner = null;
|
const criticalBanner = null;
|
||||||
|
|
||||||
|
|
@ -178,8 +179,12 @@ export default function App() {
|
||||||
onControl={submitControl}
|
onControl={submitControl}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{currentPage === 'strategy' ? (
|
{isStrategyPage ? (
|
||||||
<StrategyPage onControl={submitControl} strategy={state.dashboard.strategy} />
|
<StrategyPage
|
||||||
|
onControl={submitControl}
|
||||||
|
page={currentPage}
|
||||||
|
strategy={state.dashboard.strategy}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{currentPage === 'system' ? (
|
{currentPage === 'system' ? (
|
||||||
<SystemPage onControl={submitControl} system={state.dashboard.system} />
|
<SystemPage onControl={submitControl} system={state.dashboard.system} />
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,33 @@ const NAV_ITEMS = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
page: 'strategy',
|
page: 'strategy',
|
||||||
title: 'Strategy',
|
title: 'Strategy overview',
|
||||||
description: 'Trading state, decision flow, and guarded omissions.',
|
description: 'Quote, response, and settlement counters.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
page: 'strategy-pairs',
|
||||||
|
title: 'Pair config',
|
||||||
|
description: 'Directed pairs, edges, limits, and response policy.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
page: 'strategy-competitiveness',
|
||||||
|
title: 'Competitiveness',
|
||||||
|
description: 'Timing, quote-age, relay result, and outcome summaries.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
page: 'strategy-assets',
|
||||||
|
title: 'Asset registry',
|
||||||
|
description: 'Supported-token import and deposit addresses.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
page: 'strategy-trades',
|
||||||
|
title: 'Successful trades',
|
||||||
|
description: 'Only quotes with proven asset movement.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
page: 'strategy-lifecycle',
|
||||||
|
title: 'Quote lifecycle',
|
||||||
|
description: 'Incoming quotes and what happened next.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
page: 'system',
|
page: 'system',
|
||||||
|
|
|
||||||
|
|
@ -1203,12 +1203,11 @@ function PairConfigSection({ assetCatalog, pairConfig, onControl }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StrategyPage({ strategy, onControl }) {
|
function StrategyOverviewSection({ strategy }) {
|
||||||
const funnel = strategy.strategy_state.trade_funnel || {};
|
const funnel = strategy.strategy_state.trade_funnel || {};
|
||||||
const counts = funnel.counts || {};
|
const counts = funnel.counts || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
<div className="panel-head">
|
<div className="panel-head">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -1234,16 +1233,11 @@ export default function StrategyPage({ strategy, onControl }) {
|
||||||
<MetricCard label="Executor armed" meta={`Paused ${formatBoolean(strategy.executor_state.paused)}`} value={formatBoolean(strategy.executor_state.armed)} />
|
<MetricCard label="Executor armed" meta={`Paused ${formatBoolean(strategy.executor_state.paused)}`} value={formatBoolean(strategy.executor_state.armed)} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
<PairConfigSection assetCatalog={strategy.asset_catalog} pairConfig={strategy.pair_config} onControl={onControl} />
|
function SuccessfulTradesSection({ funnel, counts }) {
|
||||||
|
return (
|
||||||
<MakerCompetitivenessSection
|
|
||||||
pairConfig={strategy.pair_config}
|
|
||||||
summary={strategy.strategy_state.maker_competitiveness}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AssetCatalogSection assetCatalog={strategy.asset_catalog} onControl={onControl} />
|
|
||||||
|
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
<div className="panel-head">
|
<div className="panel-head">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -1261,7 +1255,11 @@ export default function StrategyPage({ strategy, onControl }) {
|
||||||
</div>
|
</div>
|
||||||
<SuccessfulTradesTable items={funnel.successful_trades} />
|
<SuccessfulTradesTable items={funnel.successful_trades} />
|
||||||
</section>
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function QuoteLifecycleSection({ items }) {
|
||||||
|
return (
|
||||||
<section className="panel full-width-evidence-panel">
|
<section className="panel full-width-evidence-panel">
|
||||||
<div className="panel-head">
|
<div className="panel-head">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -1272,8 +1270,33 @@ export default function StrategyPage({ strategy, onControl }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<QuoteLifecycleTable items={strategy.strategy_state.recent_lifecycle_rows} />
|
<QuoteLifecycleTable items={items} />
|
||||||
</section>
|
</section>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function StrategyPage({ strategy, onControl, page = 'strategy' }) {
|
||||||
|
const funnel = strategy.strategy_state.trade_funnel || {};
|
||||||
|
const counts = funnel.counts || {};
|
||||||
|
|
||||||
|
switch (page) {
|
||||||
|
case 'strategy-pairs':
|
||||||
|
return <PairConfigSection assetCatalog={strategy.asset_catalog} pairConfig={strategy.pair_config} onControl={onControl} />;
|
||||||
|
case 'strategy-competitiveness':
|
||||||
|
return (
|
||||||
|
<MakerCompetitivenessSection
|
||||||
|
pairConfig={strategy.pair_config}
|
||||||
|
summary={strategy.strategy_state.maker_competitiveness}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'strategy-assets':
|
||||||
|
return <AssetCatalogSection assetCatalog={strategy.asset_catalog} onControl={onControl} />;
|
||||||
|
case 'strategy-trades':
|
||||||
|
return <SuccessfulTradesSection counts={counts} funnel={funnel} />;
|
||||||
|
case 'strategy-lifecycle':
|
||||||
|
return <QuoteLifecycleSection items={strategy.strategy_state.recent_lifecycle_rows} />;
|
||||||
|
case 'strategy':
|
||||||
|
default:
|
||||||
|
return <StrategyOverviewSection strategy={strategy} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ const serviceCardSource = readFileSync(new URL('../src/operator-dashboard/static
|
||||||
const statusBarSource = readFileSync(new URL('../src/operator-dashboard/static/components/StatusBar.jsx', import.meta.url), 'utf8');
|
const statusBarSource = readFileSync(new URL('../src/operator-dashboard/static/components/StatusBar.jsx', import.meta.url), 'utf8');
|
||||||
const systemSource = readFileSync(new URL('../src/operator-dashboard/static/pages/SystemPage.jsx', import.meta.url), 'utf8');
|
const systemSource = readFileSync(new URL('../src/operator-dashboard/static/pages/SystemPage.jsx', import.meta.url), 'utf8');
|
||||||
const appSource = readFileSync(new URL('../src/operator-dashboard/static/App.jsx', import.meta.url), 'utf8');
|
const appSource = readFileSync(new URL('../src/operator-dashboard/static/App.jsx', import.meta.url), 'utf8');
|
||||||
|
const navSource = readFileSync(new URL('../src/operator-dashboard/static/components/NavRail.jsx', import.meta.url), 'utf8');
|
||||||
|
|
||||||
test('strategy page owns consolidated quote lifecycle and successful trade tables', () => {
|
test('strategy page owns consolidated quote lifecycle and successful trade tables', () => {
|
||||||
assert.match(strategySource, /Quote lifecycle/);
|
assert.match(strategySource, /Quote lifecycle/);
|
||||||
|
|
@ -143,10 +144,25 @@ test('strategy page distinguishes configured bps from gross quote edge percent',
|
||||||
assert.doesNotMatch(strategySource, /Edge \$\{item\.edge_bps\}%/);
|
assert.doesNotMatch(strategySource, /Edge \$\{item\.edge_bps\}%/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('pair controls are rendered before the long asset catalog table', () => {
|
test('strategy section panels are split into separate menu destinations', () => {
|
||||||
assert.ok(
|
assert.match(navSource, /Strategy overview/);
|
||||||
strategySource.indexOf('<PairConfigSection') < strategySource.indexOf('<AssetCatalogSection'),
|
assert.match(navSource, /Pair config/);
|
||||||
);
|
assert.match(navSource, /Competitiveness/);
|
||||||
|
assert.match(navSource, /Asset registry/);
|
||||||
|
assert.match(navSource, /Successful trades/);
|
||||||
|
assert.match(navSource, /Quote lifecycle/);
|
||||||
|
assert.match(navSource, /page: 'strategy-pairs'/);
|
||||||
|
assert.match(navSource, /page: 'strategy-competitiveness'/);
|
||||||
|
assert.match(navSource, /page: 'strategy-assets'/);
|
||||||
|
assert.match(navSource, /page: 'strategy-trades'/);
|
||||||
|
assert.match(navSource, /page: 'strategy-lifecycle'/);
|
||||||
|
assert.match(appSource, /currentPage\.startsWith\('strategy-'\)/);
|
||||||
|
assert.match(appSource, /page=\{currentPage\}/);
|
||||||
|
assert.match(strategySource, /case 'strategy-pairs'/);
|
||||||
|
assert.match(strategySource, /case 'strategy-competitiveness'/);
|
||||||
|
assert.match(strategySource, /case 'strategy-assets'/);
|
||||||
|
assert.match(strategySource, /case 'strategy-trades'/);
|
||||||
|
assert.match(strategySource, /case 'strategy-lifecycle'/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue