/** * Format a number as a price string with 2 decimal places and thousands separators. * e.g. 1234.5 → "1,234.50" */ export function formatPrice(n: number): string { return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2, }); } /** * Format a number as a percentage string with 2 decimal places. * e.g. 12.345 → "12.35%" */ export function formatPercent(n: number): string { return `${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2, })}%`; } /** * Format a large number with K/M/B suffix. * Values >= 1_000_000_000 → "1.23B" * Values >= 1_000_000 → "456.7M" * Values >= 1_000 → "12.3K" * Values < 1_000 → plain number, no suffix */ export function formatLargeNumber(n: number): string { const abs = Math.abs(n); const sign = n < 0 ? '-' : ''; if (abs >= 1_000_000_000) { return `${sign}${(abs / 1_000_000_000).toFixed(2).replace(/\.?0+$/, '')}B`; } if (abs >= 1_000_000) { return `${sign}${(abs / 1_000_000).toFixed(1).replace(/\.?0+$/, '')}M`; } if (abs >= 1_000) { return `${sign}${(abs / 1_000).toFixed(1).replace(/\.?0+$/, '')}K`; } return n.toString(); } /** * Format an ISO date string as a short date. * e.g. "2025-01-15T14:30:00Z" → "Jan 15, 2025" */ export function formatDate(d: string): string { const date = new Date(d); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', }); } /** * Format an ISO date string as a date with time. * e.g. "2025-01-15T14:30:00Z" → "Jan 15, 2025 2:30 PM" */ export function formatDateTime(d: string): string { const date = new Date(d); return `${date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', })} ${date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', 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); } /** * 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. * (For a bare unsigned amount there is already `formatPrice` above.) */ 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'; }