跳到正文

机器学习与收益预测

从回测 Sharpe 到可部署 Sharpe,中间有三层在漏。目标该怎么定、容量怎么把 alpha 吃掉、组合构建能补回多少,以及一个诚实的数字区间。

第 2 篇 · 共 8 篇 约 14 分钟

设定简单得可疑:拿特征,预测未来收益,根据预测建仓。

天真版本更简单——50 个特征,训一个 XGBoost,预测 5 日前向收益,做多最高十分位、做空最低十分位。30 行代码,跑出来一个看着相当炸裂的回测:Sharpe 2.0,年化 25%,最大回撤 8%,净值曲线一条平滑的右上斜线。

它也几乎肯定是纯属虚构。虚构从好几个地方同时冒出来:特征里的前视偏差、模型选择里的多重检验偏差、没建模的交易成本、没建模的容量限制,再加上测试期出现了一个训练期里根本没有的市场环境。那个 Sharpe 2.0 大约是两个单位的真实 alpha 减去一点五个单位的方法论 bug。实盘 Sharpe 是 0.5。

这是量化机器学习最常见的结果,而它有个可以说清楚的结构:回测 Sharpe、实盘 Sharpe、可部署 Sharpe 是三个不同的数,每一层之间都在漏。这篇文章讲的就是漏在哪儿,以及知道漏在哪儿之后该把时间花在什么地方。

流水线的五步,和被忽略的三步

端到端看,机器学习 alpha 流水线是五步。特征是输入矩阵,point-in-time 计算,正确处理存活、前视、缩放问题。模型是任何监督学习器——XGBoost 是默认,但线性模型、神经网络、随机森林都各有支持者。原始 alpha 信号是模型的输出:回归器给出每只票每天一个数,分类器给出一个概率,排序器给出一个名次。组合权重把原始信号变成实际仓位,可以做多顶部做空底部、等权、波动率目标、行业中性。收益是实际 P&L,扣完交易成本、融券成本、滑点。

多数研究员只盯着第 2 步做优化,然后就当交差了。杠杆在第 1 步、第 4 步和第 5 步:没有成本模型你没法评估策略,没有组合构建逻辑你没法放大策略,没有好的特征你没法改进策略。这三步恰好也是上面那三层漏水的位置。

目标定义:这是整篇文章里最便宜的一次提升

“预测收益”这个框架里藏了一个选择,而这个选择比换模型的影响大得多。

原始前向收益 rt+1,t+hr_{t+1, t+h} 是多数人第一次上手的做法:把特征回归到从 t+1t+1 到 t+ht+h 的累计收益上。离散方向 sign(rt+1,t+h)\text{sign}(r_{t+1, t+h}) 是二元分类,模型输出上涨概率。横截面名次则是该票在未来收益窗口上、在全池子里的排位。

三个里第三个是学术界和专业量化机器学习的主力,而前两个各有硬伤。

原始收益的第一个问题是目标被市场行情主导。牛市里几乎所有票的前向收益都是正的,熊市里几乎都是负的,于是模型的损失被目标均值的行情级偏移支配,而不是被横截面分辨力支配——它在学预测行情,不是在学预测票的特异 alpha。第二个问题是损失被幅度主导:少数几只 5 天涨 10% 的票会支配平方误差损失,模型被激励去把这些离群点预测准,代价是中位数的票预测得更差。第三个问题最要命,是横截面不可比:一个”票 A 未来 5 天 +3%“的预测,在一个平均票 -5% 的行情里根本没法变成仓位,你缺的是其他票会怎么走这个上下文。

离散方向的问题小一些但更直接:二元框架把幅度信息整个扔了,上涨概率 51% 的票和 99% 的票被一视同仁。

横截面名次把这三个问题一次解决。它对行情不变——牛市里排第一的票可能是全池平均 +5% 的情况下自己 +10%,熊市里排第一的票可能是全池平均 -8% 的情况下自己 -2%,模型的活儿是分辨而不是预测绝对幅度,而分辨这件事在两种行情下是同一个问题。它还直接翻译成组合:有了名次,构建就变成做多顶部十分位、做空底部十分位,不用再想绝对幅度、仓位大小、市场择时。顺带地,训练模型去预测名次(或者优化一个 pairwise ranking loss)的损失曲面比预测原始收益更平滑,梯度更友好,收敛所需的迭代更少。

标准做法是每天在横截面上对目标做秩归一化,然后训一个回归器——通常是对秩目标做回归的 XGBoost,或者 LambdaMART 那样的 pairwise ranker——模型的连续输出直接作为横截面 alpha 信号。只换目标不换任何别的东西,通常就是这条流水线上最便宜的一次 Sharpe 提升。

还有一个跟目标绑在一起的错误,犯的人同样多:策略实际持仓 5–20 天,标签却用 1 天前向收益。标签的窗口必须跟持仓周期对齐,否则模型的信号只有一部分跟你的持仓期重叠,剩下的部分就是纯噪声。每周调仓就预测 5 天收益,每月调仓就预测 21 天收益。模型和交易周期必须一致。

一个具体的设定,和它诚实的产出

假设你手里有 50 个 point-in-time 算出来的特征,覆盖 1,500 只美股,时间 2010–2024,要预测 5 日前向横截面名次。数据这样切:训练集 2010–2018,验证集 2019 用于模型选择,测试集 2020–2024 在开发期间绝对不碰。模型用 XGBoost,对秩归一化的目标做平方误差损失,默认超参(learning rate 0.05、max depth 6、500 轮、在验证集上早停)。

现实的结果大概是这样:

  • 测试集 Sharpe(毛的,扣费前):1.0–1.5
  • 测试集 Sharpe(扣完现实交易成本):0.5–1.0
  • 测试集 IC(预测值与实现前向收益的秩相关):0.02–0.05
  • 年化换手:500–1500%(每年 5–15 次)
  • 最大回撤:5–15%

IC 0.10 是稀有事件,而且几乎肯定是过拟合。如果你在测试集上看到 0.10 的 IC,最可能的解释是数据泄露,不是你天赋异禀。

把这些数字接着往下推一层:测试 Sharpe 1.5 听着挺好,扣掉交易成本(通常 -0.5 到 -1.0)、容量约束(5,000 万美元以上 AUM 通常再 -0.3)、以及信号被拥挤后不可避免的衰减(平均每年 -0.2),能部署的 Sharpe 落在 0.3–0.5 才是现实的区间。这些折扣的量级随池子和换手率变化很大,但顺序是稳定的:成本咬得最狠,容量其次,衰减最慢但不可逆。

(Gu, S., Kelly, B., & Xiu, D., “Empirical Asset Pricing via Machine Learning,” Review of Financial Studies, 33(5), 2020, pp. 2223-2273。)

容量:alpha 是怎么被自己的成交吃掉的

上一节那个 -0.3 值得单独展开,因为它是回测 Sharpe 和可部署 Sharpe 之间最大的一道闸。

可部署 Sharpe 是你的资本实际能拿到的 alpha,扣掉”交易会推动价格、你自己的成交就是价格形成过程的一部分”这一事实。关键的比值不是 AUM 本身,而是你每天在每只票上的成交额占该票日均成交额的比例。在换手率固定的情况下,这个比例随 AUM 线性上升,而市场冲击随它非线性上升——这就是容量问题的全部结构。

假设策略在组合层面每天有 10 个基点的毛 alpha,252 个交易日下来大概每年 25%。1 亿美元 AUM 时,把仓位摊到一批日均成交额几千万美元的票上,你在每只票上大约占日成交量的百分之一到二,基本能不留痕迹地进出。资金翻到 5 亿,同一批票、同样的换手,比例也跟着翻五倍,来到百分之五到十——到这个量级你已经是在跟自己对赌了,实际成交价明显比假设的差,alpha 在缩水。再往上到 20 亿,在大部分票上你都是被迫的买卖方,alpha 已经蒸发成了市场冲击。

把这个关系画出来就是容量曲线:对任何 alpha 策略,可部署 Sharpe 都是 AUM 的凹函数。一个常被用来做粗略估算的形式是

Sdeploy(A)≈Sgross/(1+κ⋅A/A0)S_{\text{deploy}}(A) \approx S_{\text{gross}} / (1 + \kappa \cdot A/A_0)

其中 κ\kappa 是容量系数(资本每翻一倍通常 0.1–0.3),A0A_0 是你能基本拿到全部毛 alpha 的那个资本量。要说清楚的是:这个函数形式是示意,不是来自某个具体来源的定理——AFML 第 14 章 §14.3 只定性描述了这种衰减,没有给出闭式。用它做数量级估计可以,别拿它去做资金分配的最终依据。

容量本身是三个东西的函数:策略的换手率、底层池子的流动性、平均单笔交易规模。一年换手 2 倍、池子是美国大盘流动股的策略可以撑到几十亿美元;一年换手 50 倍、池子是新兴市场小票的策略,撑死也就几千万美元。这两个极端之间差了两个数量级,所以”建不建容量模型”不是个可以商量的问题。

(López de Prado, M., Advances in Financial Machine Learning, 第 14 章 §14.3 General Characteristics — Capacity。ISBN 978-1119482086。)

有效的和不灵的

把上面这些 caveat 说完,剩下的问题是:到底什么有效。

有效的第一条是特征质量压过模型复杂度。30 个精心挑选、构造正确的特征过一个正则化线性回归,会在同样 30 个特征上击败 XGBoost;在 200 个噪声特征上再击败它一次;在 200 个特征里 150 个是噪声的情况下赢得更彻底。模型复杂度的边际价值小于特征质量的边际价值,这条在这个领域里几乎没有例外。

其次,梯度提升(XGBoost、LightGBM)成为默认是有原因的:它对非线性、缺失值、混合量级、特征交互都有原生支持,不用预处理,默认超参通常离最优就差百分之十左右。只要特征靠谱,这个选择基本不会错。

第三,一堆弱信号的组合比单一强信号强。50 个 IC 各为 0.02 的特征组合在一起,通常能产生比单个 IC 0.10 的特征更高的组合 Sharpe——特征层面的分散化和资产层面的分散化是同一个道理。第四,时间序列交叉验证不可省:随机 k-fold 会骗你,最低的诚实标准是带 purge 的 walk-forward。

不灵的那些也值得逐个说清楚,因为它们大多不是”完全没用”,而是”投入产出比不划算”。

用深度神经网络处理表格特征能用,但相对 XGBoost 的边际提升很小,运维复杂度却高一个数量级。神经网络真正的地盘是有空间或时序结构的另类数据——卫星图像、原始文本、订单簿;在表格特征上,XGBoost 还是老大。

没有源真验证的情绪特征噪声太大。信号是真实的,但很小,一个不做严格源筛选的”Twitter 情绪”特征捕到的主要是噪声加一点点信号,而噪声会盖过信号。

流动票上的逐笔微观结构特征信号确实存在,但已经被纳秒级延迟的高频交易室吃光了。等你的日频模型看到它的时候,它已经没了——这不是模型问题,是时间尺度问题。

直接预测波动率是另一种误配。波动率本身是可预测的(GARCH、HAR、基于 Transformer 的波动率模型都能做),但波动率这个信号比横截面收益信号弱得多。波动率模型适合作为收益模型的一个特征,不适合作为独立 alpha。

最后是模型 stacking。用一个 meta-learner 套在基模型预测之上,在基模型捕捉到不同信号时有用;在基模型高度相关时——多数机器学习 alpha 都是这种情况——反而有害。堆 10 个不同随机种子的 XGBoost 通常什么也得不到。

组合构建:模型之外的另一半

模型的输出是每只票每天一个分数。把这个分数变成实际仓位是一个独立的问题,而且出奇地关键——组合构建的选择能明显改变一个策略的 Sharpe,跟模型预测力无关。

最简单的构建是按模型分数排序,做多顶部十分位、做空底部十分位,桶内等权。这样得到的是美元中性组合,多空两边名义相等。这里有一个几乎人人踩过的坑:美元中性不等于市场中性。一个等权的做多顶部、做空底部组合仍然可能有明显的市场 beta 和行业暴露,尤其当模型的顶部十分位系统性地偏向某个因子的时候。要真的市场中性,需要额外的显式 beta 中和,通常还要加上行业或风险因子的中和。朴素构建的另一个问题是它完全没有风控:集中的行业暴露会带来行业特有的回撤,集中的单票暴露会带来特异性爆雷,Sharpe 还行但路径很糙。

往上加的第一层是波动率目标:把每只仓位按近期波动率反比缩放,也就是 wi∝αi/σiw_i \propto \alpha_i / \sigma_i,其中 αi\alpha_i 是模型分数,σi\sigma_i 是票 ii 的近期已实现波动率。一只年化波动率 60% 的票,仓位是 30% 那只的一半。组合整体波动率更稳定,风险调整后的表现通常有可观改善。

第二层是行业中性:在算仓位之前把每个行业内的平均分数减掉,组合对任何行业都没有净敞口。这把通常并非故意的行业下注干掉了,也压住了行业轮动带来的回撤。代价要说清楚——你是在押注行业中性调整之外的那部分 alpha 能赚钱。有些行业会有大幅、持续的走势(2022 年的能源、2023 年的科技),行业中性组合是主动错过的。这是一次有意识的取舍:用行业 alpha 换更干净的风险因子暴露。

第三层是风险因子中性,做法是把票的收益对标准风险因子(Fama-French、动量、波动率)做回归,再把 alpha 信号对这些因子载荷做正交化。组合对标准因子零敞口,收益完全来自模型的残差 alpha。这是”我想要一个不依赖价值或动量因子的市场中性组合”的严格版本,也是组合构建里最激进的形态——你在赌模型捕捉到了标准因子集之外的东西。因子中性通常要吃掉两三成的毛 alpha,所以在付这个代价之前,先确认那个赌是成立的。

最后是仓位大小:对最有信心的票和边缘票各放多少。线性仓位(仓位与 alpha 分数成正比)是默认;幂次仓位(scorek\text{score}^k,k>1k > 1)把仓位集中到高信心票上;平方根仓位(score\sqrt{\text{score}})是中间档。k=2k = 2 在好模型上通常能改善 Sharpe,在差模型上反而拉低——仓位大小的激进程度本质上是你对模型质量的押注,而这种押注很难只从回测里说清楚。

说到底,组合构建带来的提升大部分来自风控,不是来自仓位大小的奇技淫巧。一个有波动率目标、行业中性、朴素线性仓位的组合,几乎每次都能打赢一个有花哨仓位方案但风控烂的组合。

超参调优:别把超参也过拟合了

搭完模型之后最自然的冲动是去调超参——learning rate、max depth、min child weight、正则化强度、列采样、行采样。现代梯度提升库暴露 15–20 个超参,组合空间大得吓人。

风险在于你是在验证集上调,而验证集是有限的。试了 1,000 个组合,总有几个看起来很好;你挑了最好的那个,上线,跑输了。你过拟合的是超参,机制和过拟合模型一模一样。

有纪律的做法是四条。第一,划一个小网格:每个超参 3–5 个值,每次只动 2–3 个超参,组合总数控制在 50–100,忍住扩张的冲动。第二,在训练集内部用交叉验证选超参,而不是用验证集——在训练时间段内,按 purged CV 各折的平均表现来挑。第三,把验证集锁给模型选择:训练集内选完超参之后,验证集用来在模型之间做决定(XGBoost 还是 LightGBM、原始目标还是秩归一化目标),并且只动一次。第四,接受默认超参通常就够了——XGBoost 上 learning rate 0.05、max depth 6、min child weight 1、subsample 0.8、colsample_bytree 0.8、500 轮配早停,在大多数量化任务上离最优只差 5–10%,花几小时调它们很少能赢默认值赢得够多,值得冒那份过拟合的风险。

超参调优是一种让自己看起来很忙、其实没在动杠杆的方式。特征、目标定义、数据质量、组合构建,每一项都比它重要得多。

五十个特征,然后就到头了

跑过足够多次收益预测实验之后,哪些特征类别贡献最多信号,规律相当清楚。下面这些 IC 数字是量级上的经验,随池子和年代变化不小,但相对次序是稳定的。

动量和反转特征是横截面收益预测的最大单一贡献者。一个只用 5–10 个精心构造的动量特征——原始收益、跳月、风险调整、行业相对、剔除市场后的残差——单凭这些就能拿到 0.02–0.03 量级的 IC,而再往上加动量变体,超过十个之后基本是收益递减。

波动率和风险特征是第二大贡献者。特异波动率(票的收益在剔除市场和因子暴露之后的波动率)是收益的强负向预测因子;对标准因子的 beta 提供温和信号;尾部风险度量(偏度、峰度、期望损失)加一点小但真实的量。

基本面估值特征(账面市值比、收益价格比、自由现金流收益率)在价值倾向的行情里贡献显著信号,其他时候温和。它的信号是真实的,但跟行情挂钩——一个价值特征这十年贡献 0.01 的 IC,换一个十年可能只有一半。质量特征(盈利能力、应计、盈余稳定性)贡献温和,而它最大的价值出现在和价值特征组合的时候,“合理价格下的好质量”是一个有据可查的效应。

情绪和另类特征在横截面里贡献的是小量信号——单个情绪特征的量级在 0.002 上下,十个加起来大概 0.005–0.008,有意义但不主导。宏观特征(利率、通胀、信用利差、期限结构)在同质池子里通常不贡献横截面信号,它们的用武之地是预测因子组合之间的相对表现,比如价值对成长、低波动对高波动。

把这些加起来就是一条实用基线:20 个动量和波动率特征,10–15 个基本面特征,5–10 个质量特征,有数据的话再加 5–10 个另类特征。这个 50 特征的模型是横截面收益预测的主力,任何更复杂的方案都应该先跟它打一场。

而它同时也是一条天花板。机器学习量化里很自然的演进路径是 10 个特征、50 个、200 个、500 个,每往前走一步 IC 都会升一点——开头快,然后收益递减,最后纹丝不动。经验上,横截面收益预测的大部分信号被前 30–50 个特征就抓完了;过了这个数,加进来的东西加噪声比加信号更快。在美股上,一个精心搭的 50 特征模型 IC 大约停在 0.04–0.06,同架构的 500 特征模型是 0.04–0.07。多出来的 450 个特征边际价值极小,过拟合风险极大。

所以规矩是:先搭 30–50 个特征,量一下 IC,只有在想清楚了某个具体信号的时候才加新特征。别”加进来看看有没有用”。每个新特征都是一个需要验证的假设,一次加 100 个未验证的特征等于一次做 100 个对比检验,看起来像信号的那些大部分会是噪声——这也正是本文开头那个虚构的 Sharpe 2.0 最初诞生的地方。

(Harvey, C. R., Liu, Y., & Zhu, H., “…and the Cross-Section of Expected Returns,” Review of Financial Studies, 29(1), 2016, pp. 5-68。)