← 博客

大模型生成的量化因子怎么验证?上实盘前的七道检查

2026年10月30日

English
目录
  1. 1. 记录每一次尝试,包括扔掉的那些
  2. 2. 模型可能已经知道测试区间的结果
  3. 3. 留出集不进生成循环
  4. 4. 检查代码,不看描述
  5. 5. 看它新增了什么,而不是它好不好
  6. 6. 扣成本,按你实际会交易的规模算
  7. 7. 上资金前先跑模拟盘
  8. 大模型在这里擅长什么

让大模型提因子,它给得很快:一个定义,一段为什么可能有效的解释,再加一段算这个因子的 pandas 代码。再问一次,又是二十个。把它和回测框架接成一个循环,跑一晚上能出几百个候选。

这些都已经不难了。难的是判断哪些候选是真的。而且生成的候选,出错的方式和人手写的不太一样:模型可能早就知道测试区间的行情是怎么走的;代码算的可能不是描述里说的那个东西;每个想法几乎不花成本,所以你实际试了多少次,增长得比你自己感觉的快得多。

下面是我的量化系统里,一个生成的因子或策略在拿到任何资金之前要过的检查。按顺序跑,哪一道没过就停在哪一道。

大模型生成因子 + 代码便宜、快、量大1. 记录每一次尝试试得越多,门槛越高2. 只在模型训练截止日之后测它可能知道答案3. 留出集不进生成循环只用一次4. 检查代码,不看描述已知答案、不读未来5. 看它新增了什么扣掉已有因子之后6. 扣成本、看容量收益 − 换手 × 成本7. 上资金前先跑模拟盘和实盘同一条代码路径哪一道没过,就停在哪一道。

1. 记录每一次尝试,包括扔掉的那些

回测出来的 Sharpe,只有和“为了得到它试了多少次”放在一起看才有意义。手写因子的时候,这个次数天然很小,因为写一个要花一天。换成生成循环,它可以涨到几百,而且没有人专门决定过“我要做几百次检验”。

为了看清这件事有多严重,我做了个模拟:一批真实收益为零的策略,日收益全是噪声,每个五年,从 N 个里挑年化 Sharpe 最高的那个。重复很多次取平均:

00.51.01.50.00.71.11.5N = 1101001,000尝试次数 · 纯噪声里最好的年化 Sharpe,5 年日频数据

纯噪声试一千次,挑出来最好的那个,五年的年化 Sharpe 在 1.5 左右。很多真策略能有这个数都会很满意。这个模拟假设每次尝试相互独立;生成的因子之间经常高度相似,所以有效次数比原始次数少,但绝不会是 1。

我的做法:

  • 循环产出的每个候选都在日志里记一行,包括还没跑完整回测就被淘汰的。次数是结果的一部分。
  • 通过门槛随次数提高。正式的做法是 deflated Sharpe ratio;就算只用粗略版本,问一句“在这里,N 个噪声策略里最好的长什么样”,也能挡掉大部分问题。
  • 先把高度相似的候选归成一组,让次数反映的是不同的想法,而不是改了名字的副本。

2. 模型可能已经知道测试区间的结果

这一条在手写因子里没有对应。一个用某个日期之前的文本训练出来的模型,读过那之前发生的事:哪些行业涨了,哪些公司暴雷了,哪些异象被写成了论文。让它提一个因子,再拿同一段年份去测,所谓的“发现”可能只是模型在复述它读过的东西。

这件事不需要你在 prompt 里写股票代码或日期才会发生。模型如果知道某类股票在某段时间表现好,它提出的因子就会倾向于选中这类股票。

我的规则是:生成的因子只用模型训练截止日之后的数据来评判。更早区间的结果保留下来做基本检查,比如代码能不能跑、方向是否合理,但从不算作证据。这会让可用的测试区间短很多,这是用这个工具必须付的代价。

还有一点适用于任何从公开研究里来的因子:论文里写过的异象,发表之后往往会变弱。大模型对“什么有效”的判断很大程度上来自这些文献,所以它的建议会偏向市场已经有时间去交易掉的那些想法。

3. 留出集不进生成循环

常见的循环是这样的:生成一个因子,回测,把结果写回 prompt(“这个 Sharpe 不行,换个变体试试”),再来一轮。这个循环本身就是在拟合。它的回测跑在哪段数据上,那段数据就是训练数据,哪怕整个过程没有算过一次梯度。

所以留出区间对循环完全不可见:不给分数,不给通过与否,也不给一句摘要。它只在最后用一次,留给其他检查全部通过的候选。如果看完留出集之后我又改了这个候选,再看一次,这段留出集就作废了,下一段只能从更晚的数据里取。

4. 检查代码,不看描述

生成的因子会附带一段很笃定的英文描述,而代码未必和描述一致。描述说“20 日动量,跳过最近一周”;代码要么跳错了那一头,要么在整个历史上做排序,而不是每天截面排序。回测评估的是代码,你记住的却是描述。

三项检查能抓住大部分这类问题:

  • 已知答案测试:造一个很小的合成数据集,因子值可以手算出来,然后对比。
  • 改未来测试:把输入数据里日期 t 之后的值全部改掉,重新计算。t 当天及之前的因子值必须一个都不变。如果变了,说明代码读到了未来的数据:可能是居中的滚动窗口,可能是用全样本做标准化,也可能是按错的日期做了 join。这个测试是机械的,能抓住读代码时漏掉的泄漏。
  • 按时点取数:财务数据要按它公开的日期对齐,而不是按它所属的报告期。生成的代码几乎总是用报告期,因为网上的示例代码大多这样写。

回测的通用陷阱,比如未来函数、幸存者偏差、成本,这些照样适用,我整理在回测陷阱这一课里。生成代码改变的是数量:没有人会用眼睛审三百个因子函数,所以这些检查必须自动跑。

5. 看它新增了什么,而不是它好不好

很多生成的因子是已有因子换了个算法:窗口稍微不同的动量,分母换了一下的价值因子。它们回测好看,是因为被模仿的那个因子本来就有效。

所以要问的不是“这个因子的 Sharpe 好不好”,而是“它在我已有的东西之外增加了什么”。实际做法是:把它的收益对已有因子和行业暴露做回归,看剩下的部分。单独表现很强、扣完之后什么都不剩的候选直接丢掉。表现一般、但残差干净且相关性低的候选,反而更值得看。

6. 扣成本,按你实际会交易的规模算

生成的策略容易往高换手的方向走,因为短周期的模式能给循环最多的信号。只看毛 Sharpe 说明不了什么:要先按换手率和真实的单笔成本,从每一期收益里扣掉交易成本,再用扣完的收益重新计算 Sharpe。

我给每个候选套用实盘系统用的成本模型,按它实际会下的仓位大小算,毛收益和净收益并排放。两者差得很大,这本身就是一个结论:说明结果更依赖成本假设,而不是信号。

7. 上资金前先跑模拟盘

最后一道检查,是让候选走和实盘完全相同的代码路径,订单只记录不发送,跑足够长的时间,再和回测在同一段时间里应该给出的结果对比。对比的方法和我对实盘账户用的一样:把研究和实盘的差距拆成几项、加起来正好等于总差额。这样少赚的部分能追到信号、执行还是成本上,而不是一句“比预期差一点”。

大模型在这里擅长什么

上面这些都不是在反对用大模型做研究。以前慢的那些环节,它做得很好:把一个模糊的想法变成精确的定义,写出第一版代码,列出一个因子可能被污染的各种方式,还有起草上面这些测试。它在量化研究里更广泛的用法,可以看这一章的综述。

它做不到的,是降低一个策略需要的证据。如果说有影响,它反而提高了这个要求:想法的数量上去了,模型还读过测试区间的一部分。上面这些检查,就是一个生成的候选要和我手写的候选一视同仁之前,必须先过的关。