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:
@@ -72,3 +72,69 @@ export function formatDateTime(d: string): string {
|
||||
hour12: true,
|
||||
})}`;
|
||||
}
|
||||
|
||||
// ── Metric display helpers ─────────────────────────────────────────────────
|
||||
// Shared by the Signals backtest/paper-trade panels. Dashboard and
|
||||
// OpenTradesPanel deliberately still carry their own copies — migrating them is
|
||||
// a separate change, not drive-by scope.
|
||||
|
||||
/** R-multiple with an explicit sign. e.g. 1.2 → "+1.20R", null → "—" */
|
||||
export function fmtR(v: number | null | undefined): string {
|
||||
if (v === null || v === undefined) return '—';
|
||||
return `${v > 0 ? '+' : ''}${v.toFixed(2)}R`;
|
||||
}
|
||||
|
||||
/** e.g. 12.34 → "12.3%" */
|
||||
export function fmtPct(v: number | null | undefined): string {
|
||||
return v === null || v === undefined ? '—' : `${v.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
/** e.g. 12.34 → "+12.3%" */
|
||||
export function fmtSignedPct(v: number | null | undefined): string {
|
||||
if (v === null || v === undefined) return '—';
|
||||
return `${v > 0 ? '+' : ''}${v.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
/** Always rendered negative, whatever sign the source uses. 17.3 → "-17.3%" */
|
||||
export function fmtDrawdown(v: number | null | undefined): string {
|
||||
return v === null || v === undefined ? '—' : `-${Math.abs(v).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
/** e.g. 15.3 → "15.3d" */
|
||||
export function fmtDays(v: number | null | undefined): string {
|
||||
return v === null || v === undefined ? '—' : `${v.toFixed(1)}d`;
|
||||
}
|
||||
|
||||
/** Unitless ratios — Sharpe, Sortino, Calmar, Gain/Pain, profit factor. */
|
||||
export function fmtRatio(v: number | null | undefined): string {
|
||||
return v === null || v === undefined ? '—' : v.toFixed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bare amount, no currency symbol and no sign. e.g. 1234.5 → "1,234.50"
|
||||
* Kept separate from fmtSignedMoney on purpose — they are not interchangeable.
|
||||
*/
|
||||
export function fmtMoney(v: number | null | undefined): string {
|
||||
if (v === null || v === undefined) return '—';
|
||||
return v.toLocaleString('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Signed currency, using U+2212 for negatives. e.g. -12.3 → "−$12.30"
|
||||
* Use wherever a value can go negative and the unit is money.
|
||||
*/
|
||||
export function fmtSignedMoney(v: number | null | undefined): string {
|
||||
if (v === null || v === undefined) return '—';
|
||||
return `${v >= 0 ? '+' : '−'}$${Math.abs(v).toFixed(2)}`;
|
||||
}
|
||||
|
||||
/** Green above zero, red below, neutral at zero or null. */
|
||||
export function rColor(v: number | null | undefined): string {
|
||||
if (v === null || v === undefined) return 'text-gray-400';
|
||||
if (v > 0) return 'text-emerald-400';
|
||||
if (v < 0) return 'text-red-400';
|
||||
return 'text-gray-300';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user