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:
2026-08-11 22:09:23 +02:00
co-authored by Claude Opus 5
parent 442dc3f04b
commit 13a984a84d
10 changed files with 599 additions and 397 deletions
@@ -5,8 +5,7 @@ import { triggerJob, resetTrackRecord } from '../../api/admin';
import { Button } from '../ui/Button';
import { Disclosure } from '../ui/Disclosure';
import { useToast } from '../ui/Toast';
import { BacktestPanel } from './BacktestPanel';
import { MyTradesPanel } from './MyTradesPanel';
import { fmtR, rColor } from '../../lib/format';
// Need at least this many matured setups before the pipeline check means anything;
// below it the live sample is too noisy to compare.
@@ -16,18 +15,6 @@ const DRIFT_TOLERANCE_R = 0.2;
type PipelineStatus = 'building' | 'tracking' | 'drift' | 'no-backtest';
function fmtR(value: number | null): string {
if (value === null) return '—';
return `${value > 0 ? '+' : ''}${value.toFixed(2)}R`;
}
function rColor(value: number | null): string {
if (value === null) return 'text-gray-400';
if (value > 0) return 'text-emerald-400';
if (value < 0) return 'text-red-400';
return 'text-gray-300';
}
function StatusChip({ status }: { status: PipelineStatus }) {
const styles: Record<PipelineStatus, { cls: string; label: string }> = {
tracking: { cls: 'border-emerald-500/30 bg-emerald-500/15 text-emerald-300', label: '✓ in sync' },
@@ -39,7 +26,7 @@ function StatusChip({ status }: { status: PipelineStatus }) {
return <span className={`shrink-0 rounded-full border px-2.5 py-1 text-xs font-medium ${s.cls}`}>{s.label}</span>;
}
export function TrackRecordPanel() {
export function EvaluationPanel() {
const queryClient = useQueryClient();
const toast = useToast();
@@ -101,19 +88,14 @@ export function TrackRecordPanel() {
return (
<div className="space-y-6">
{/* Your real, realized results come first; the strategy simulation follows. */}
<MyTradesPanel />
<div className="border-t border-white/[0.06]" />
<BacktestPanel />
<Disclosure summary="Track-record maintenance">
<Disclosure summary="Setup-grading diagnostic & maintenance">
<div className="space-y-4 pt-1">
<p className="max-w-2xl text-xs text-gray-500">
<span className="text-amber-300/90">Diagnostic only not production P&amp;L.</span>{' '}
Grades gate-level touch vs stop (the rejected take-profit model). Production exits are
initial stop / ATR trail / max hold see paper trades and the portfolio monitor above.
Target before stop = win, stop first = loss (same-bar both = loss), neither in 30 trading
days = expired at 0R. Only matured windows count. Scores{' '}
initial stop / ATR trail / max hold see the Paper Trades tab and the portfolio monitor
above. Target before stop = win, stop first = loss (same-bar both = loss), neither in 30
trading days = expired at 0R. Only matured windows count. Scores{' '}
<span className="text-gray-300">all</span> setups as a control group; runs nightly.
</p>