动量方舟 · Momentum Ark

多策略动量轮动研究平台
本文是系列《从 0 到量化平台》的第 7 篇,共 13 篇。查看目录 →
2026-10-30 从0到量化平台事故净值双AI验证

第 7 篇:第三次大事故——假暴跌、另一个 AI 的报告,和一个"对账员"

早上 10 点,三条告警和一个"外援"

10 月 30 日上午,我打开手机,先看到的是前一天 16:30 的推送:

  • 永久组合净值 1.4714 → 1.3805,跌 6.18%
  • 全天候组合 1.4396 → 1.3528,跌 6.03%
  • 永久组合回撤 16.47%,超过 15% 阈值

三个告警,同一个方向。但我盯着它们的第一反应不是"市场怎么了",而是——这两个组合里头大部分是债券和黄金,一天怎么可能跌 6%?

于是我做了一件事:把三条告警和三份备份文件丢给另一个 AI(扣子),让它独立分析;同时把这个分析贴给 WorkBuddy:

> 我(10-30 10:01):你昨天 4:30 报了三个告警,我让扣子分析了一下,你看看:(附完整报告)

这就是"双 AI 交叉验证":一个给结论,另一个带着数据库去现场验证。

另一个 AI 查到了什么

扣子的报告写得很清楚,我摘几条关键的:

> 三条告警全是误报,平台没跌,是净值算错了一次。

告警说的实际差
永久组合 1.4714 → 1.3805(-6.18%)1.4714 → 1.4686(-0.20%)虚报 6%
全天候 1.4396 → 1.3528(-6.03%)1.4396 → 1.4341(-0.38%)虚报 5.7%
永久回撤 16.47%11.1%(低于 15% 阈值)不该报

其余 16 个策略当天全部正常,最大波动是半导体网格的 -2.14%。

它把根因链也拆出来了,我按时间顺序复述:

10 月 28 日 16:30,采集端误判。 513500(标普 500ETF)当天发生 1:2 份额折算,前复权价从 5.404 变成 2.702。平台的"行情异常防护"把它当成数据错误,乘 2 还原成 5.404 存库。因为份额还是旧口径(32,744 份),市值算出来 176,949 元——是对的,当天净值 1.4715 也没错。

10 月 29 日 09:32–10:07,修了一半。 有人发现还原判断错了,把这只 ETF 的全部历史价格 ÷2 归一到 2.702 口径。备份文件名 app.db.pre_scale_20261029_0932、app.db.post_scale_20261029 就是这次操作留下的痕迹。

但 positions.holdings.units 里的份额没有同步 ×2,还是 32,744 份。

10 月 29 日 16:30,净值腰斩。 净值 = 份额 × 收盘价。32,744(旧份额)× 2.688(新价格)= 88,016 元,正确应该是 176,032 元——凭空少了一半。总资产从 1,468,603 掉到 1,380,587,净值 1.3805。再拿这个数和 1 月的高点 1.6527 比,回撤 16.47%,第三条告警也来了。

16:30–17:59,份额被改成 65,488,净值重算回 1.4686 / 1.4341。

三份备份文件的对比,我把它们摆在一起看,链条清清楚楚:

时点513500 份额总资产
09:32 pre_scale32,744.121,471,449.9
10:07 post_scale32,744.12(没改)1,471,449.9
17:59 备份65,488.23(×2 了)1,468,561.4

这个洞,是四天前我自己问出来的

说来讽刺:这次事故的起点,是我 10 月 26 日的一个追问。

> 我(10-26 09:58):我问你个问题啊,我感觉你的价格 adj_close 和 close 完全相等,是纯价格口径,是不是没有用复权价

这条消息在记录里出现了 6 次。核心就一句:你存的价格,是"没复权"的原始价,还是"复权"过的价?

分红、拆分会改变价格,但你的资产没变。用不复权的价格算净值,等于每次分红都当成一次下跌——十年下来,收益会被系统性低估。这个不改,所有回测都不作数。

它查了,确认是纯价格口径。我问工作量,然后拍了板:

> 我(10-27 10:26):听你的,先干 P0-1 + P0-3

于是有了那次复权改造:历史价格全量刷新,adj_close 换成前复权口径。

改造本身是对的,但它给数据表带来了"两种口径可以共存"的可能。 四天后,513500 的份额折算撞上这个可能,就成了假暴跌。

这也是自动化项目里最难防的一类问题:你今天做的一个正确决定,会在四天后以另一种形式咬你一口。 不是因为它错,而是因为它只改了一半的世界。

但我没有照单全收

这份报告写得太完整了,完整到让人想直接转发。可我还是让 WorkBuddy 去服务器上逐条核对——因为报告是推理出来的,账是真在库里的。

核对结果是:根因链是对的,但两处细节错了:

1. 扣子说"全盘找不到对应脚本、bash_history 里也没有,说明是纯手工执行"——不对。那次归一其实有脚本,只是脚本本身少做了一步(没同步份额)。这比"手工操作"更值得注意:有脚本也会漏,因为脚本可以只写一半。 2. 扣子说 17:59"自动修正"——也不对。那一次是手工改回来的,系统并没有自愈。不存在"自愈"这件事,别给自己留这种幻觉。

这两处纠正看似细节,其实改变了整改方向:如果是"人手工改库",结论是"禁止手工";如果是"脚本少一步",结论是"让脚本必须原子完成"。方向不一样,补丁就不一样。

三个漏洞,三条规则

报告里提了三个隐患,我基本认同,落地成了三条规则:

规则一:价格表只允许存一种口径。 source 字段的记录显示,这只 ETF 在 10-20 到 10-28 是前复权(tencent_adj),10-29 变成了不复权(tencent)。前复权价会随分红拆分自动重算历史,不复权不会。两个口径混在一张表里,下次除权日一定再出问题。

规则二:任何价格口径的整体缩放,必须原子完成。 所谓原子,就是"要么全做,要么全不做"。一次缩放必须在同一个事务里做完三件事:

1. 缩放历史价格 2. 对应标的的持仓份额按反向因子同步调整(价格 ÷k → 份额 ×k) 3. 触发所有策略的信号重算

中间态被 16:30 的净值计算读到,就是这次这种假暴跌。

规则三:净值写库前,必须做一致性校验。 原来的顺序是"先写库 → 再比阈值 → 报警",等于把错误数据当成了事实。改成:单策略单日跌幅超过 3% 时,先看是哪个标的贡献的、该标的当天真实跌幅是多少。如果策略跌了 5%,但没有任何一个持仓标的跌超 5%,那就不是市场跌了,是你算错了——拒绝写库,报数据级告警。

我们后来把这个角色叫"对账员":它不负责赚钱,只负责在账不平的时候拒绝签字。

顺手抓到的第四个漏洞

整改过程中,WorkBuddy 主动汇报了一件事:告警入口少接了一根线——调用时缺一个参数,一旦触发会直接报错中断。

而这个入口,恰好只在"采集出异常"的那天才被调用。也就是说:平时从来不执行,一出事就崩。最需要它喊人的时候,它先哑了。

这个 bug 让我后背发凉。它意味着:过去半个月里,所有"采集异常"的日子,我可能一条告警都没收到,而系统自己也不知道。

这一篇学到的

1. 两个 AI 互相校对,是普通人能用的最高级手段。 A 给结论,我拿去让 B 带着数据现场验证,听 B 反驳,再拍板。AI 给结论,我去现场。 任何一份"看起来很完整"的分析,都值得被另一双眼睛查一次。

2. 报告写得越顺,越要核对。 扣子那份报告逻辑通顺、时间线完整、还列了整改建议——它错的两处,恰好是最容易被"顺"过去的地方(谁来修的、怎么修的)。

3. 手工修和脚本修,是两种病。 手工修的问题是会漏步骤;脚本修的问题是会漏写步骤。前者靠流程,后者靠"原子化"设计。

4. 最危险的 bug 在异常路径上。 正常路径天天跑,问题早暴露了;异常路径一年跑三次,没人测。而它偏偏只在出事时才被调用。

【你也可以】

1. 让 AI 给的任何重要结论,换个说法贴给另一个 AI:"帮我检查这份分析哪里可能错,特别是因果链。" 2. 给系统加一句"算不平就别写"——宁可今天没有数据,也不要把错的当成事实。 3. 检查你的自动化里,异常处理那段代码有没有被测过。有没有可能:它从来没成功跑过?


本系列为个人研究与搭建记录,所有数据与策略均不构成投资建议。

数据仅供参考,不构成任何投资建议。市场有风险,投资需谨慎。

其他笔记