feat(strategy): 代码版本快照(§12.6补,方案B发起时快照·QuantConnect轻量版)——用户拍板:解决「实例参数有快照但代码没有,历史运行无法回溯当时跑的哪版代码」——①sanguo_api/code_versions.py:发起时全文落盘data/strategy_code_versions/{file}.{hash8}.py(md5内容寻址天然去重,原子替换防并发坏);发起四入口(paper/live/CTA/组合回测)全快照,账户加code_hash列(ALTER迁移),回测经spec→run_meta.code_hash落档案②查看:GET /strategy/code-versions列表+单版本全文;代码编辑页「版本历史」抽屉(MonacoDiff左右对比当前,主题同款)③「代码已变更」角标:策略库档案行(enriched code_changed)+全景每run标v哈希·一致/已变更④双轨对账加「代码版本一致」第五指标(对账FAIL先查这行,两边代码不同价差必然大);路径穿越防护(版本号只认8位hex)+6测试;927绿+build绿;「用当时代码重跑」留P2 [vps]
CI/CD / test (push) Successful in 13s
CI/CD / nas-deploy (push) Successful in 1m1s
CI/CD / nas-verify (push) Successful in 19s

This commit is contained in:
2026-08-15 23:38:07 +08:00
parent 1103ae85d5
commit 6b07389c85
20 changed files with 549 additions and 18 deletions
+17 -3
View File
@@ -410,17 +410,31 @@ export interface RunningAccountMock {
drifted?: boolean
}
export const strategyInstancesEnrichedMock = {
instances: (strategyInstancesMock.instances as Array<StrategyInstance & { running_accounts?: RunningAccountMock[]; drift?: boolean }>).map((i) => ({
instances: (strategyInstancesMock.instances as Array<StrategyInstance & { running_accounts?: RunningAccountMock[]; drift?: boolean; code_changed?: boolean }>).map((i) => ({
...i,
running_accounts: i.status.paper_live === 'running'
? [{ kind: 'paper', aid: 101, label: i.name, ret: i.last_return }]
? [{ kind: 'paper', aid: 101, label: i.name, ret: i.last_return, code_version: 'a1b2c3d4', code_changed: i.id === 3 }]
: (i.status.live === 'running'
? [{ kind: 'live', aid: 201, label: `${i.name}·live`, ret: i.last_return }]
? [{ kind: 'live', aid: 201, label: `${i.name}·live`, ret: i.last_return, code_version: 'a1b2c3d4', code_changed: false }]
: []),
drift: i.id === 2,
code_changed: i.id === 3,
})),
}
/* §12.6 补:代码版本快照 mock */
export const codeVersionsMock = (file: string) => ({
file,
versions: [
{ code_version: 'a1b2c3d4', saved_at: '2026-08-14 21:05:02', size: 8213 },
{ code_version: '9f8e7d6c', saved_at: '2026-08-10 20:31:44', size: 7920 },
],
})
export const codeVersionMock = (file: string, h8: string) => ({
file, code_version: h8,
code: `# ${file} 快照 v${h8}mock\n\ndef initialize(context):\n pass\n`,
})
export function instanceOverviewMock(id: number) {
const inst = strategyInstancesMock.instances.find((i) => i.id === id) || strategyInstancesMock.instances[0]
const days = ['07-20', '07-21', '07-22', '07-23', '07-24', '07-25', '07-26', '07-27', '07-28']
+3
View File
@@ -31,6 +31,9 @@ const routes: Route[] = [
{ m: 'get', re: /^\/strategy\/files$/, build: () => D.strategyFilesMock },
{ m: 'post', re: /^\/strategy\/file\/[^/]+\/check$/, build: () => ({ ok: true }) },
{ m: 'post', re: /^\/strategy\/file\/[^/]+$/, build: () => ({ ok: true }) },
// §12.6 补:代码版本快照(列表在前,单版本 4 段路径在后)
{ m: 'get', re: /^\/strategy\/code-versions$/, build: (url) => D.codeVersionsMock(new URL(url, 'http://x').searchParams.get('file') || '') },
{ m: 'get', re: /^\/strategy\/code-versions\/[^/]+\/[^/]+$/, build: (url) => { const seg = url.split('/').filter(Boolean); return D.codeVersionMock(decodeURIComponent(seg[2]), seg[3]) } },
// strategy configsCRUD
{ m: 'get', re: /^\/strategy\/configs$/, build: () => D.strategyConfigsMock },
{ m: 'post', re: /^\/strategy\/configs$/, build: () => ({ id: 99 }) },