1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
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<Run['status'], string> = {
queued: '排队中',
running: '执行中',
succeeded: '已完成',
failed: '失败',
cancelled: '已取消'
};
export const runStatusBadge: Record<Run['status'], 'grey' | 'warn' | 'ok' | 'danger' | 'teal'> = {
queued: 'grey',
running: 'warn',
succeeded: 'ok',
failed: 'danger',
cancelled: 'grey'
};
export const datasetStatusLabel: Record<Dataset['status'], string> = {
pending: '等待中',
running: '获取数据',
ready: '就绪',
failed: '失败'
};
export const adjustmentLabel: Record<string, string> = {
none: '不复权',
qfq: '前复权',
hfq: '后复权'
};
export const assetTypeLabel: Record<string, string> = {
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<string, string> = {
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)
];
}
|