import type { BacktestMetrics, Dataset, Instrument, Run } from './types'; export function uuidLike(id: string): boolean { return /^[0-9a-f-]+$/i.test(id); } export function fmtDateTime(iso?: string | null): string { if (!iso) return '—'; const d = new Date(iso); if (isNaN(d.getTime())) return iso; return d.toLocaleString('zh-CN', { hour12: false }); } export function fmtDate(iso?: string | null): string { if (!iso) return '—'; return iso.replace('T', ' ').replace(/Z$|\+08:00$/i, ''); } export function fmtPct(v: number | null | undefined, digits = 2): string { if (v === null || v === undefined || Number.isNaN(v)) return '—'; return `${(v * 100).toFixed(digits)}%`; } export function fmtNum(v: number | null | undefined, digits = 2): string { if (v === null || v === undefined || Number.isNaN(v)) return '—'; if (Math.abs(v) >= 1e8) return `${(v / 1e8).toFixed(2)} 亿`; if (Math.abs(v) >= 1e4) return `${(v / 1e4).toFixed(2)} 万`; let r = v.toFixed(digits); if (r === '0.00' && v !== 0) r = '0'; return r; } export function itemSourceLabel(itemSource?: string | null, fallback = ''): string { const s = itemSource || fallback; if (!s) return '—'; if (s === 'catalog') return '目录'; if (s === 'provider_suggest') return '供应商'; if (s === 'eastmoney') return '东财'; if (s === 'tencent') return '腾讯'; if (s === 'sina') return '新浪'; return s; } export const runStatusLabel: Record = { queued: '排队中', running: '执行中', succeeded: '已完成', failed: '失败', cancelled: '已取消' }; export const runStatusBadge: Record = { queued: 'grey', running: 'warn', succeeded: 'ok', failed: 'danger', cancelled: 'grey' }; export const datasetStatusLabel: Record = { pending: '等待中', running: '获取数据', ready: '就绪', failed: '失败' }; export const adjustmentLabel: Record = { none: '不复权', qfq: '前复权', hfq: '后复权' }; export const assetTypeLabel: Record = { stock: '股票', etf: 'ETF', index: '指数' }; export function instrumentLabel(i: Instrument): string { const name = i.name ? ` ${i.name}` : ''; return `${i.market}.${i.symbol}${name}`; } export function formatMoney(v: number | null | undefined): string { if (v === null || v === undefined || Number.isNaN(v)) return '—'; return `¥ ${fmtNum(v)}`; } export const versionSourceLabel: Record = { manual: '手动快照', run: '回测快照', ai: 'AI 采用', restore: '恢复产生' }; export function metricsLabels(): string[] { // trade_count semantics (worker/backtest.py): COUNT of closed round trips — // an open that is fully closed afterwards counts as 1; fills/orders and // cancellations do NOT count. Consistent label for the result card AND the // run comparison tables. return ['总收益率', '年化收益率', '最大回撤', '夏普比率', '平仓回合数', '期末权益']; } export function metricsRow(m: BacktestMetrics): string[] { return [ fmtPct(m.total_return), fmtPct(m.annual_return), fmtPct(m.max_drawdown), m.sharpe === null || m.sharpe === undefined ? '—' : m.sharpe.toFixed(2), m.trade_count === null || m.trade_count === undefined ? '—' : String(m.trade_count), m.final_equity === null || m.final_equity === undefined ? '—' : fmtNum(m.final_equity) ]; }