refactor(signals): split the Track Record tab and cut the backtest page down
One tab stacked three things that all called themselves a track record: realized paper P&L, setup-outcome grading under the rejected take-profit model, and the backtest portfolio simulation. Split into Setups | Paper Trades | Backtest, one subject each. `track` stays the Paper Trades slug so the legacy /performance redirect keeps working. The grading diagnostic and its Evaluate / Reset controls go with Backtest, not Paper Trades — reset_track_record deletes trade_setups, not paper trades. BacktestPanel 439 -> 175 lines. Its run settings alone were 106 lines of hand-rolled sr-only radio cards for two binary choices; they are now two Dropdowns and a button on one wrapping row, with the per-option prose moved into the existing explainer. The amber warnings survive as a conditional slot, so a non-default choice still announces itself but the common path is silent. The recommendation printed eight findings at equal weight, burying the verdict in tuning detail. `topic` now splits them: production, benchmark and robustness stay inline, gate/exit/cutoff collapse behind a disclosure, and any WARNING or LAGS item is promoted out of the collapsed group regardless of topic. No topic chips — every backend string already self-prefixes, so a chip would render "GATE | Gate: ...". Portfolio metrics are now two tiers: five headline tiles for what the book returned, then a smaller labelled row for how good that return was (Sortino, Calmar (MAR), Gain/Pain, Profit Factor $, EV/trade). Reports cached before those metrics existed hide the second row rather than showing a half-populated line of dashes. Extracted EquityCurveChart, PortfolioMonitorPanel and BacktestRecommendationCard, plus a StatTile primitive and shared formatters for the duplication in the files this touched. DashboardPage and OpenTradesPanel deliberately keep their own copies — migrating them is separate scope. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,31 +1,61 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { PageHeader } from '../components/ui/PageHeader';
|
||||
import { Tabs } from '../components/ui/Tabs';
|
||||
import { SetupsPanel } from '../components/signals/SetupsPanel';
|
||||
import { TrackRecordPanel } from '../components/signals/TrackRecordPanel';
|
||||
import { MyTradesPanel } from '../components/signals/MyTradesPanel';
|
||||
import { BacktestPanel } from '../components/signals/BacktestPanel';
|
||||
import { EvaluationPanel } from '../components/signals/EvaluationPanel';
|
||||
|
||||
const tabs = ['Setups', 'Track Record'] as const;
|
||||
const tabs = ['Setups', 'Paper Trades', 'Backtest'] as const;
|
||||
type Tab = (typeof tabs)[number];
|
||||
|
||||
// `track` stays the Paper Trades slug: App.tsx redirects the legacy /performance
|
||||
// route to ?tab=track, and that is where realized results live.
|
||||
const SLUG_TO_TAB: Record<string, Tab> = {
|
||||
track: 'Paper Trades',
|
||||
backtest: 'Backtest',
|
||||
};
|
||||
const TAB_TO_SLUG: Record<Tab, string> = {
|
||||
Setups: '',
|
||||
'Paper Trades': 'track',
|
||||
Backtest: 'backtest',
|
||||
};
|
||||
const SUBTITLE: Record<Tab, string> = {
|
||||
Setups: 'Detected trade setups from the latest scan',
|
||||
'Paper Trades': 'What the strategy actually delivered on trades you took',
|
||||
Backtest: 'Whether the promoted strategy is worth trading, replayed over history',
|
||||
};
|
||||
|
||||
export default function SignalsPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const activeTab: Tab = searchParams.get('tab') === 'track' ? 'Track Record' : 'Setups';
|
||||
const activeTab: Tab = SLUG_TO_TAB[searchParams.get('tab') ?? ''] ?? 'Setups';
|
||||
|
||||
const setTab = (tab: Tab) => {
|
||||
setSearchParams(tab === 'Track Record' ? { tab: 'track' } : {}, { replace: true });
|
||||
const slug = TAB_TO_SLUG[tab];
|
||||
setSearchParams(slug ? { tab: slug } : {}, { replace: true });
|
||||
};
|
||||
|
||||
const body: Record<Tab, ReactNode> = {
|
||||
Setups: <SetupsPanel />,
|
||||
'Paper Trades': <MyTradesPanel />,
|
||||
// The backtest and the diagnostic that checks it against live outcomes.
|
||||
Backtest: (
|
||||
<div className="space-y-6">
|
||||
<BacktestPanel />
|
||||
<EvaluationPanel />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-slide-up">
|
||||
<PageHeader
|
||||
title="Signals"
|
||||
subtitle="Detected trade setups and how past signals actually performed"
|
||||
/>
|
||||
<PageHeader title="Signals" subtitle={SUBTITLE[activeTab]} />
|
||||
|
||||
<Tabs tabs={tabs} active={activeTab} onChange={setTab} />
|
||||
|
||||
<div className="animate-fade-in" key={activeTab}>
|
||||
{activeTab === 'Setups' ? <SetupsPanel /> : <TrackRecordPanel />}
|
||||
{body[activeTab]}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user