AI 结对开发工作流:把审查变成一段能跑的代码
10 条规则扫本课自己的 91 个文件,命中 73 处——而其中大部分是"合理的",这正是它该被登记而不是被删掉的原因
和 AI 结对写量化代码,最大的风险不是"写不出来",是——
写出来跑得通、但结论是错的。
而本课前面反复出现的那些错误,有相当一部分是有固定文本形态的:
随机 K 折 · 全样本 fit_transform · fillna(0) · 用 .max()/.min() 定参数
center=True 的滚动窗 · 前复权做回测 · 分析脚本里直接联网取数
既然有固定形态,就可以用 linter 扫出来,而不是靠每次 review 时的记性。
10 条规则扫本课自己的 91 个 .py 文件:
QL001 随机 K 折 1 处
QL002 全量 fit_transform 1 处
QL004 缺失值填 0 25 处
QL005 居中滚动窗口 2 处
QL007 用全样本极值定参数 3 处
QL009 分析脚本内联网取数 41 处
合计 73 处
而其中大部分是"合理的"——这恰恰是它们该被登记、而不是该被删掉规则的原因。
机场安检不理解你的行李里装了什么,它只认形状和材质。
所以它会把充电宝、剃须刀、水瓶都拦下来——大部分是无害的。
但没人会因此说"安检没用",也没人会因为水瓶太多就把液体检测关掉。
正确的做法是:解释、登记、放行。
一、规则从哪来:本课自己踩过的坑
| 规则 | 检查什么 | 出处 | 实测代价 |
|---|---|---|---|
| QL001 | KFold(shuffle=True) | p7.4 | 20 日标签虚高 +0.1224 AUC |
| QL002 | fit_transform(X) 对全量 | p7.4 | 对线性模型直接失效 |
| QL003 | train_test_split 未关 shuffle | p7.4 | 默认打乱时序 |
| QL004 | .fillna(0) | p5.3 / p1.6 | 分层挤成并列块(某层 20.9 只 vs 59.5 只) |
| QL005 | rolling(center=True) | p1.2 | 窗口包含未来数据 |
| QL006 | 特征侧出现 shift(-n) | p1.2 | 未来函数 |
| QL007 | close.max()/min() 定参数 | p6.5 | 年化差 1.80~3.64 pp |
| QL008 | adjustflag=2(前复权) | p1.3 | 历史价格会变,回测不可复现 |
| QL009 | 分析脚本内 yf.download/ak.*/bs.query_ | p1.5 / p5.9 | ERC 期末倍数在 3.38~3.95 漂 |
| QL010 | np.random.*(未固定种子) | p1.5 | 结果不可复现 |
每一条都不是从教科书抄的,是这门课自己踩过、并且量化过代价的。
二、扫描结果:大部分命中是"合理的"
对本课全部 91 个 .py 扫描,命中 73 处。逐类看:
| 规则 | 命中 | 这些命中合理吗 |
|---|---|---|
| QL009 联网取数 | 41 | 合理——全部在 build_*.py 里,它们就是落盘脚本 |
QL004 fillna(0) | 25 | 多数合理——如 signal.shift(1).fillna(0),首日无信号即空仓 |
| QL007 全样本极值 | 3 | 合理——grid_trial.py 里是故意演示未来函数(p6.5) |
| QL001 随机 K 折 | 1 | 合理——leak_cv.py 里是故意做反面实验(p7.4) |
| QL005 居中滚动 | 2 | 需逐条核 |
| QL002 全量 fit_transform | 1 | 需逐条核 |
命中 ≠ 有错。命中的意思是「这一行需要一个解释」。
解释清楚之后的正确处理是:
r = ak.fund_open_fund_info_em(...) # quant-lint: ok(这是落盘脚本,联网是它的职责)
加白名单注释,而不是删规则。
这与本课 i18n 工具那张"刻意差异登记表"是同一套思路:
持续报红会让人忽略真告警。所以要把"已确认无害"的登记下来,而不是把规则关掉。
一个天天报 73 条红的 linter,和一个没有 linter,效果是一样的。
🔧 动手做:让 AI 审查变成一段能跑的代码(5 分钟)
跑 python quant_lint.py。它是一个量化专用 linter(10 条规则:联网取数、fillna(0)、全样本统计、未 shift…),扫本课自己全部 91 个 .py 文件。
你会看到(实测):命中 73 处——但看一眼就会发现,大部分是"合理的"(比如取数节本来就要联网、演示节故意用 fillna(0))。
想明白:这正是关键——一个报出 73 条红、其中大半是误报的工具,会让人直接不看它(和 p7.6 的体检工具、p5.x 的告警是同一个失败模式)。正确做法不是把规则删松,是给合理命中登记豁免、让剩下的红都是真问题。把"人肉审查"变成一段能跑、能复算的代码——这才是 AI 结对开发的正确姿势:AI 生成、规则把关、你只看真正的红。
三、它的价值边界
它不理解语义,只匹配已知的危险模式。
| 它能查 | 它查不了 |
|---|---|
随机 K 折、全量 fit_transform、fillna(0) | 你拿什么跟什么比(P6 整章) |
| 前复权、居中窗口、未固定种子 | 基准是价格指数还是全收益(p6.2) |
| 分析脚本里的联网调用 | 定投与一次性的总投入是否相同(p6.4) |
| 用全样本极值定参数 | 智能定投实际投出了多少(p6.6) |
它的价值不在于自动找出所有错误,在于把"审查清单"从人的记忆里搬到 CI 里。
四、AI 结对开发的四步工作流
| 步骤 | 做什么 | 为什么 |
|---|---|---|
| ① 规格 | 先写 p5.1 那样的规格对象(七类必答) | 没有规格,AI 会替你做默认选择——那就是 p5.1 的 16 种读法 |
| ② 测试 | 先写测试再写实现:无未来函数 / 双路对账 / 成本单调 / 可复现 | p4.10 实测:测试抓住过我自己写错的断言 |
| ③ 实现 | 让 AI 写实现,人只审「它做了什么假设」 | AI 最容易在你没说的地方替你决定 |
| ④ 审查 | 跑 linter + 人工审「口径」那一层 | linter 能查形态,查不了口径 |
第 ③ 步是最反直觉的一步:审代码不如审假设。
AI 写出来的代码通常语法正确、结构清晰、甚至有注释。难的从来不是看懂它写了什么,而是看出它替你决定了什么。
本课自己的例子:
- p5.3 第一版用
fillna(0)合成因子——代码完全正确,假设是错的("缺失 = 中性") - p7.4 第一版用随机森林演示标准化泄漏——代码完全正确,假设是错的(以为树会受标准化影响)
- p6.5 的网格若用全样本极值定区间——代码完全正确,假设是错的(以为区间是"已知的")
三次都不是 bug,三次都跑得通,三次结论都是错的。
五、把它接进 CI
# 提交前跑一次;有「高」级别新增命中就拒绝合并
python quant_lint.py ./src && echo "审查通过"
配合 p4.10 那套测试:
测试套件(16 个) → 查「行为对不对」
quant_lint(10 条) → 查「有没有已知的危险形态」
人工审口径 → 查「拿什么跟什么比」
三层里,只有前两层能自动化。第三层永远需要人——而这门课的大半篇幅都在训练第三层。
📌 免责:本课为技术教学,linter 规则均来自本课实测过的错误形态,不保证覆盖所有问题;不构成投资建议。
四件事:
- 本课自己踩过的坑,有固定文本形态,可以被 linter 扫出来。10 条规则的每一条都不是抄教科书,而是这门课量化过代价的:随机 K 折 +0.1224 AUC、全样本极值 1.80~3.64 pp、联网取数导致 ERC 倍数在 3.38~3.95 漂
- 扫本课自己的 91 个文件命中 73 处,而大部分是"合理的":41 处联网取数全在
build_*.py(那是它们的职责)、3 处全样本极值在grid_trial.py(故意演示未来函数)、1 处随机 K 折在leak_cv.py(故意做反面实验) - 命中 ≠ 有错——命中的意思是「这一行需要一个解释」。正确处理是加
# quant-lint: ok(理由)白名单,而不是删规则。一个天天报 73 条红的 linter,和没有 linter 效果一样 - 四步工作流:规格 → 测试 → 实现 → 审查。其中第 ③ 步最反直觉:审代码不如审假设——本课的 p5.3、p7.4、p6.5 三个例子,代码全部正确、假设全部错误、结论全部是错的
价值边界要说清:linter 能查形态,查不了口径。"你拿什么跟什么比"这一层(P6 整章)没有任何工具能自动检查。
三层防线里只有前两层能自动化:测试查行为 · linter 查形态 · 人工查口径。而这门课的大半篇幅,都在训练第三层。
下一节是 P7 的收尾:AI 辅助研究的红线——幻觉防护、结果可回溯,以及一句必须说清的话:研究参考 ≠ 交易信号。
🔎 来源与核验· 1 条,点开核对
