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>
63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
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 { MyTradesPanel } from '../components/signals/MyTradesPanel';
|
|
import { BacktestPanel } from '../components/signals/BacktestPanel';
|
|
import { EvaluationPanel } from '../components/signals/EvaluationPanel';
|
|
|
|
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 = SLUG_TO_TAB[searchParams.get('tab') ?? ''] ?? 'Setups';
|
|
|
|
const setTab = (tab: Tab) => {
|
|
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={SUBTITLE[activeTab]} />
|
|
|
|
<Tabs tabs={tabs} active={activeTab} onChange={setTab} />
|
|
|
|
<div className="animate-fade-in" key={activeTab}>
|
|
{body[activeTab]}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|