It was lifted from BacktestPanel during the extraction, then lost its last caller in the same branch when avg_trade_pnl became an EV / trade tile rendered with fmtSignedMoney and disappeared from the monitor footnote. formatPrice already covers a bare unsigned amount if one is ever needed again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
/**
|
||
* 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';
|
||
}
|