让大模型提因子,它给得很快:一个定义,一段为什么可能有效的解释,再加一段算这个因子的 pandas 代码。再问一次,又是二十个。把它和回测框架接成一个循环,跑一晚上能出几百个候选。
这些都已经不难了。难的是判断哪些候选是真的。而且生成的候选,出错的方式和人手写的不太一样:模型可能早就知道测试区间的行情是怎么走的;代码算的可能不是描述里说的那个东西;每个想法几乎不花成本,所以你实际试了多少次,增长得比你自己感觉的快得多。
下面是我的量化系统里,一个生成的因子或策略在拿到任何资金之前要过的检查。按顺序跑,哪一道没过就停在哪一道。
1. 记录每一次尝试,包括扔掉的那些
回测出来的 Sharpe,只有和“为了得到它试了多少次”放在一起看才有意义。手写因子的时候,这个次数天然很小,因为写一个要花一天。换成生成循环,它可以涨到几百,而且没有人专门决定过“我要做几百次检验”。
为了看清这件事有多严重,我做了个模拟:一批真实收益为零的策略,日收益全是噪声,每个五年,从 N 个里挑年化 Sharpe 最高的那个。重复很多次取平均:
纯噪声试一千次,挑出来最好的那个,五年的年化 Sharpe 在 1.5 左右。很多真策略能有这个数都会很满意。这个模拟假设每次尝试相互独立;生成的因子之间经常高度相似,所以有效次数比原始次数少,但绝不会是 1。
我的做法:
- 循环产出的每个候选都在日志里记一行,包括还没跑完整回测就被淘汰的。次数是结果的一部分。
- 通过门槛随次数提高。正式的做法是 deflated Sharpe ratio;就算只用粗略版本,问一句“在这里,N 个噪声策略里最好的长什么样”,也能挡掉大部分问题。
- 先把高度相似的候选归成一组,让次数反映的是不同的想法,而不是改了名字的副本。
2. 模型可能已经知道测试区间的结果
这一条在手写因子里没有对应。一个用某个日期之前的文本训练出来的模型,读过那之前发生的事:哪些行业涨了,哪些公司暴雷了,哪些异象被写成了论文。让它提一个因子,再拿同一段年份去测,所谓的“发现”可能只是模型在复述它读过的东西。
这件事不需要你在 prompt 里写股票代码或日期才会发生。模型如果知道某类股票在某段时间表现好,它提出的因子就会倾向于选中这类股票。
我的规则是:生成的因子只用模型训练截止日之后的数据来评判。更早区间的结果保留下来做基本检查,比如代码能不能跑、方向是否合理,但从不算作证据。这会让可用的测试区间短很多,这是用这个工具必须付的代价。
还有一点适用于任何从公开研究里来的因子:论文里写过的异象,发表之后往往会变弱。大模型对“什么有效”的判断很大程度上来自这些文献,所以它的建议会偏向市场已经有时间去交易掉的那些想法。
3. 留出集不进生成循环
常见的循环是这样的:生成一个因子,回测,把结果写回 prompt(“这个 Sharpe 不行,换个变体试试”),再来一轮。这个循环本身就是在拟合。它的回测跑在哪段数据上,那段数据就是训练数据,哪怕整个过程没有算过一次梯度。
所以留出区间对循环完全不可见:不给分数,不给通过与否,也不给一句摘要。它只在最后用一次,留给其他检查全部通过的候选。如果看完留出集之后我又改了这个候选,再看一次,这段留出集就作废了,下一段只能从更晚的数据里取。
4. 检查代码,不看描述
生成的因子会附带一段很笃定的英文描述,而代码未必和描述一致。描述说“20 日动量,跳过最近一周”;代码要么跳错了那一头,要么在整个历史上做排序,而不是每天截面排序。回测评估的是代码,你记住的却是描述。
三项检查能抓住大部分这类问题:
- 已知答案测试:造一个很小的合成数据集,因子值可以手算出来,然后对比。
- 改未来测试:把输入数据里日期 t 之后的值全部改掉,重新计算。t 当天及之前的因子值必须一个都不变。如果变了,说明代码读到了未来的数据:可能是居中的滚动窗口,可能是用全样本做标准化,也可能是按错的日期做了 join。这个测试是机械的,能抓住读代码时漏掉的泄漏。
- 按时点取数:财务数据要按它公开的日期对齐,而不是按它所属的报告期。生成的代码几乎总是用报告期,因为网上的示例代码大多这样写。
回测的通用陷阱,比如未来函数、幸存者偏差、成本,这些照样适用,我整理在回测陷阱这一课里。生成代码改变的是数量:没有人会用眼睛审三百个因子函数,所以这些检查必须自动跑。
5. 看它新增了什么,而不是它好不好
很多生成的因子是已有因子换了个算法:窗口稍微不同的动量,分母换了一下的价值因子。它们回测好看,是因为被模仿的那个因子本来就有效。
所以要问的不是“这个因子的 Sharpe 好不好”,而是“它在我已有的东西之外增加了什么”。实际做法是:把它的收益对已有因子和行业暴露做回归,看剩下的部分。单独表现很强、扣完之后什么都不剩的候选直接丢掉。表现一般、但残差干净且相关性低的候选,反而更值得看。
6. 扣成本,按你实际会交易的规模算
生成的策略容易往高换手的方向走,因为短周期的模式能给循环最多的信号。只看毛 Sharpe 说明不了什么:要先按换手率和真实的单笔成本,从每一期收益里扣掉交易成本,再用扣完的收益重新计算 Sharpe。
我给每个候选套用实盘系统用的成本模型,按它实际会下的仓位大小算,毛收益和净收益并排放。两者差得很大,这本身就是一个结论:说明结果更依赖成本假设,而不是信号。
7. 上资金前先跑模拟盘
最后一道检查,是让候选走和实盘完全相同的代码路径,订单只记录不发送,跑足够长的时间,再和回测在同一段时间里应该给出的结果对比。对比的方法和我对实盘账户用的一样:把研究和实盘的差距拆成几项、加起来正好等于总差额。这样少赚的部分能追到信号、执行还是成本上,而不是一句“比预期差一点”。
大模型在这里擅长什么
上面这些都不是在反对用大模型做研究。以前慢的那些环节,它做得很好:把一个模糊的想法变成精确的定义,写出第一版代码,列出一个因子可能被污染的各种方式,还有起草上面这些测试。它在量化研究里更广泛的用法,可以看这一章的综述。
它做不到的,是降低一个策略需要的证据。如果说有影响,它反而提高了这个要求:想法的数量上去了,模型还读过测试区间的一部分。上面这些检查,就是一个生成的候选要和我手写的候选一视同仁之前,必须先过的关。