某 私募 量化研究组的季末复盘。组长把 沪深300 上跑了一个季度的两条信号摊在桌上:一条是上一课构造的 12-1 月动量,样本外 21 日 rank IC 约 0.03;另一条是组里 ML 工程师用 LightGBM 训出来的 ranker,同一个 universe、同一段样本外、同一个 21 日远期 rank-return,样本外 rank IC 跳到 0.08。"翻了一倍多。" 组长说,"为什么动量自己跑只有 0.03,丢进 LightGBM 就翻倍?是它捕捉到了什么非线性?" ML 工程师点头:"是 mom_12_1 与 value_bm 在低波动状态下的交叉项 —— 高动量低估值在低波动期才赚钱,在高波动期反而是反向的。" 组长又问:"你做 5 折切分时,embargo 设了多少天?" "21 天。" "feature_leakage、label_leakage、cross_sectional_leakage、target_encoding_leakage 这四类你审过了吗?" "审过了。" 组长在记事本上记下:"先信。" —— 这一段对话就是本课的开题:ML-driven 信号能拿到非线性 alpha,但它引入了公式化与事件驱动家族都没有的三个自由度(target、horizon、loss),并且带来了四类专属的泄露模式。本课把这件事讲透。
五个不可省略的设计决策
任何一条 ML-driven 信号都要显式声明五个设计决策,缺一不可:
(1) target —— 远期收益变量。常见选择是横截面 rank-return at 跨度 h、回归 target、分类 target(top vs bottom quintile)、或一个自定义的 Sharpe 调整指标。target 本身就是信号的定义,换 target 等价于换信号。
(2) horizon —— 预测窗口 h。短 1 到 5 日适合高换手 features;中等 21 到 63 日是股票因子的 sweet spot;长 252 日+ 适合基本面风格。horizon 决定 target 的信噪比。
(3) sample_weighting —— 等权(naive,会偏向高波动期与大盘)、波动率加权、rank-target(规范修复)。
(4) loss —— pointwise MSE(回归 baseline)、pairwise rank loss(LambdaRank,与截面下注结构对齐)、listwise NDCG。
(5) leakage_controls —— 来自 4.2.1 第 2 课的 purged-and-embargoed k 折,加上下面讲的四类 ML 专属泄露模式。
without all five specified explicitly, the signal is not reproducible —— 这五项中任何一项没写明,信号就不可复现。
四类 ML 专属泄露模式
ML 管线会比公式化与事件驱动家族多出四类专属的泄露,按出现频率排:
feature_leakage—— 某个 feature 用到了预测日之后的信息。基本面 feature 把 fiscal-period-end 当 join key 是这一类;event-driven feature 把 event_date 当 availability_date 也是这一类;alt-data feature 把 period_covered 当 release_date 还是这一类。L2 的 PIT 纪律、L3 的 availability-date 纪律与 release-date 纪律统统适用。label_leakage—— 远期收益窗口在训练样本里重叠。t与t+1两个预测目标都依赖t+h的价格,共享信息。修复是 4.2.1 第 2 课的 purged-and-embargoed k 折:embargo_days >= h与purge_days >= h。cross_sectional_leakage—— 归一化把目标股票自己的值算进自己的统计量。截面 rank-normalisation、z-scoring、industry-mean-encoding 都可能犯这种 bug。修复办法是 leave-one-out 归一化或 out-of-fold 归一化。target_encoding_leakage—— 把类别 features(行业、市值组)用历史收益做 mean-encoding 时,如果用到测试集数据就是 leakage。修复办法是 out-of-fold mean-encoding,只用训练折的数据估均值。
规范 ML 栈六步
把一条规范的 ML-driven 信号写完整,要按下面六步:
(1) feature_engineering —— 把 L2 的 ~150 个公式化 features(Alpha101 加 Alpha158 的子集)+ L3 的 ~50 个事件驱动 features + ~50 个宏观与行业 features + ~50 个技术指标 拼接成约 200 到 300 列的特征矩阵。
(2) train_val_test_split —— 4.2.1 第 2 课的 purged-and-embargoed 5-fold k 折,embargo_days = horizon,purge_days = horizon。
(3) hyperparameter_search —— Optuna 或 Hyperopt,每个 trial 都要计入 4.2.1 第 3 课的 trial counter;规范预算是 100 到 300 trials。
(4) training —— LightGBM 的 lambdarank 是 2020-2025 的实战默认,CatBoost / XGBoost 是对照点。
(5) evaluation —— 在 out-of-sample stack 上的滚动 3 月 rank IC,>0.06 月度是实战门槛("模型确实做出了点东西")。
(6) production —— 每月按 3 年滚动训练窗口重训;IC 衰减到样本内 50% 以下时触发"退役或重训"。
LightGBM ranker 规范超参
实战里 LightGBM ranker 的规范调用就是这一行:
import lightgbm as lgb
model = lgb.LGBMRanker(objective='lambdarank', metric='ndcg', num_leaves=31, learning_rate=0.05, n_estimators=500)
LightGBM with lambdarank objective is the practitioner default for cross-sectional equity-factor ML signals 2020-2025 —— 五个超参 num_leaves=31、learning_rate=0.05、n_estimators=500、objective='lambdarank'、metric='ndcg' 是过去五年实战 baseline 的稳定起手式。
端到端工作例
把整个 ML 栈写成一段可复现的训练循环。函数签名固定,实现按五步走:
from typing import List, Tuple
import lightgbm as lgb
def train_lightgbm_ranker(X: pd.DataFrame, y: pd.Series, dates: pd.Series, horizon: int = 21, n_splits: int = 5) -> Tuple[List[lgb.LGBMRanker], pd.Series]:
# 1. 构造截面 group sizes —— 每个日期的横截面规模
groups = dates.value_counts().sort_index().tolist()
# 2. 构造 purged-and-embargoed k 折
cv = PurgedEmbargoedKFold(n_splits=n_splits, embargo_days=horizon, purge_days=horizon, dates=dates)
models, oos = [], pd.Series(index=y.index, dtype=float)
# 3. 逐折训练
for tr, te in cv.split(X, y):
group_sizes_train = compute_groups(dates.iloc[tr])
m = lgb.LGBMRanker(objective='lambdarank', metric='ndcg', num_leaves=31, learning_rate=0.05, n_estimators=500)
m.fit(X.iloc[tr], y.iloc[tr], group=group_sizes_train)
# 4. OOS predictions
oos.iloc[te] = m.predict(X.iloc[te])
models.append(m)
# 5. 返回模型列表与拼接好的 OOS 预测
return models, oos
把这个函数跑在 沪深300 上、样本期 2018-2023、horizon=21,期望的滚动 3 月 OOS rank IC 在 0.06 到 0.10 之间。如果你拿到 0.20,先怀疑泄露,不要先发表。
特征工程的实战细节
把 200 到 300 列特征拼起来不是简单 concat,实战里有几个细节经常被忽略。第一,对齐 PIT 时间戳 —— L2 的基本面特征要按公告日 join,L3 的事件驱动特征要按 availability_date join,alt-data 特征要按 release_date join。三套时间戳并行,任何一列对错都会让整个特征矩阵带前视偏差。第二,缺失值处理 —— LightGBM 原生支持缺失值,不要事先用 0 或者中位数填,会偏离模型对缺失的处理。第三,特征缩放 —— 树模型对单调变换不敏感,所以不必做 z-score;但要做截面 rank,把不同尺度的特征拉到同一个相对量纲。第四,类别特征处理 —— 行业、市值组这种类别变量用 out-of-fold mean-encoding,绝不能用全样本 mean-encoding,否则就是 target_encoding_leakage。
把这四件事写成一段标准的特征拼装函数,在 沪深300 上跑两小时就能跑完一整个样本期。规范的做法是把这个 ETL 落到一个独立的 batch 任务里,每天凌晨触发,产出当天的特征矩阵切片,落库到 feature_store(Feast / Tecton 类),再由训练管线消费。这是工程化的一面,本课只点到。
生产监控
把 LightGBM ranker 跑到生产之后,监控比训练还重要。规范监控项三条:
(1) IC 衰减 —— 滚动 3 月 OOS rank IC 跌破样本内 50% 的水位时触发"退役或重训"。
(2) 特征分布漂移 —— 监控每一列特征的截面均值、方差、缺失率的时序变化。某个特征突然偏离常态,通常意味着数据源的口径发生了变化。
(3) 预测分布漂移 —— 监控模型输出的横截面均值、方差与分位数。一旦预测分布从正态变得高度偏态,说明模型在异常状态下做出了极端预测。
这三条监控指标都不放在本课的练习里,但实战中比训练时的 IC 数字更能决定一条 ML 信号是否还活着。
跨度敏感性
ML ranker 对 target horizon 的敏感性是另一张必做的表:
# LightGBM ranker horizon sensitivity —— horizon_days
horizon_grid = [5, 21, 63, 252]
for h in horizon_grid:
models, oos = train_lightgbm_ranker(X, y_for_horizon(h), dates, horizon=h)
ic = rolling_3m_rank_ic(oos, forward_returns_h(h))
print(h, ic)
诠释规则:"the medium-horizon (21-63 day) regime is the canonical equity-factor sweet spot; short horizons (1-5 day) require intraday features and have high turnover; long horizons (252-day+) have stable but lower in-sample IC" —— 21 到 63 日的中等跨度是股票因子的规范 sweet spot;短跨度需要日内特征、换手率高;长跨度样本内 IC 稳但偏低。
神经网络替代方案
LightGBM 不是 ML 信号的唯一选择,只是 2020-2025 这五年实战里的默认起手式。神经网络替代方案在两个场景下更有竞争力:第一,当特征矩阵里包含 时序结构 (例如电话会议文本 embedding、订单簿 snapshot 序列),LSTM 或 transformer encoder 可以在 embedding 层学到 LightGBM 无法直接消化的时序依赖。第二,当 特征数量 上到数千列(典型 alt-data feature pipeline),宽 MLP 加正则可能比 GBDT 更好地分摊高维稀疏信号。但这两种场景在主流股票因子研究里并不是常态,深度网络的训练成本与可复现性都比 LightGBM 高,所以本课只把它点到名为止——具体的神经网络架构设计在 2.6 ML 理论模块里讲。
特征重要性诊断也是 ML 信号常见但被高估的一个工具。SHAP、permutation importance、López de Prado 的 MDI / MDA 分解都能跑出来一张漂亮的图,但在多重共线下(典型情况:mom_12_1 与 mom_6_1 高度相关),特征重要性是 不可识别 的。规范做法是先把高度相关的特征聚类,再报告聚类层面的重要性。本课只点到为止。
ML 家族与公式化、事件驱动家族的区别
ML-driven 家族与前两者最大的差别在 设计决策的数量。公式化信号由"公式 + 归一化栈"完全决定;事件驱动信号由"事件检测规则 + 衰减函数"完全决定;ML-driven 信号还多出 target、horizon、loss 这三个自由度,而且这三个自由度都不靠学术文献固定,每一个都是研究员自己拍的。这意味着 ML 信号的 trial counter 增长速度比公式化信号快一个数量级 —— 一条公式化信号的回看敏感性是 5 到 10 个 trial,一条 ML 信号的超参搜索是 100 到 1000 个 trial。4.2.1 第 3 课的 deflated-Sharpe 校正在 ML 家族里变得至关重要。
也正因为这种自由度,ML 家族特别容易把 因子模型 经典的 因子暴露 关系破坏掉。当 LightGBM 在 mom_12_1 与 value_bm 之间学到非线性交叉时,信号的 因子暴露(factor loading)就不再是这两个 因子模型 leg 的线性叠加,而是一个状态依赖的非线性映射。这条性质在评估阶段会让 IC 看起来更高,但也让 Alpha 衰减 更不稳定 —— 一旦市场状态切换,模型可能瞬间失效。下一模块"信号评估与组合"会专门讲怎么诊断这种状态依赖。
与 4.2.3 评估模块的衔接
把一条 ML 信号训出来只完成了"构造"这一半。剩下的"评估"是下一个模块的事:IC 时间序列、IR、衰减曲线、换手率、容量。本课用的 rolling 3 month rank IC 只是诊断级,真正的 IR-net 计算 要把交易成本算进去 —— 一条 21 日 horizon LightGBM ranker 的月度换手率典型 50% 到 80%,10 bp round-trip 成本下每年的成本拖累就有 6% 到 10%。
衔接
整个 4.2.2 模块到这里收官。L1 给了你 DSL 与三家族分类;L2 把公式化库构造完;L3 把事件驱动与另类数据库构造完;本课把这两库当作特征喂给 LightGBM ranker,产出 ML-driven 信号。下一模块 4.2.3 会教你怎么把这些信号一一评估,然后把它们组合起来。
本课组件
Inline-code listing of the five non-optional design decisions:target、horizon、sample_weighting、loss、leakage_controls。Inline-code listing of the four ML-specific leakage modes:feature_leakage、label_leakage、cross_sectional_leakage、target_encoding_leakage。Inline-code listing of the six canonical ML-stack steps:feature_engineering、train_val_test_split、hyperparameter_search、training、evaluation、production。Inline-code listing of the LightGBM-ranker canonical hyperparameters:lightgbm.LGBMRanker(objective='lambdarank', metric='ndcg', num_leaves=31, learning_rate=0.05, n_estimators=500)。Fenced python 段:train_lightgbm_ranker 端到端函数(horizon=21、n_splits=5 缺省)与 [5, 21, 63, 252] 四点跨度敏感性表。一个总结 Exercise,两个 Hint。Region anchors:沪深300 universe 与 大股东 增减持 / 巨潮 信息 网。本课在词汇层覆盖的因子标签:动量、价值因子、质量因子、规模因子、低波动因子;以及 因子模型 框架、因子暴露 在 ML 信号下的非线性化提醒、Alpha 衰减 在 ML 信号下的状态依赖性提醒。完整评估框架(IC、IR、衰减曲线、换手率、容量)是下一模块的内容。
Fenced python 生产监控提醒:滚动 3 月 rank IC 跌破样本内 50% 即触发"退役或重训"。
总结练习
Exercise
在 沪深300 universe 上,样本期 2018-2023,端到端构建并评估一条 截面股票因子 LightGBM ranker。(i) 显式声明五个设计决策:target (21 日远期截面 rank-return)、horizon (21 日)、sample_weighting (rank-target,等权)、loss (LightGBM lambdarank 的 pairwise rank loss)、leakage_controls (purged-and-embargoed 5 折 k 折,embargo=21、purge=21,加四类 ML 专属泄露模式各显式应对一次)。(ii) 构造特征矩阵 X,约 50 列:25 列 L2 公式化(动量、价值、质量、成长、投资、杠杆,来自 Alpha158)+ 15 列 L3 事件驱动(PEAD / 回购 / 内部人 / 分析师修订)+ 10 列宏观或行业。(iii) 对每一列 feature 审一遍四类 ML 专属泄露:对每列指出哪一类是最高风险并写出规范防御。(iv) 训练 lightgbm.LGBMRanker(objective='lambdarank', metric='ndcg', num_leaves=31, learning_rate=0.05, n_estimators=500),在每一折上跑。(v) 报告 OOS stack 的滚动 3 月 rank IC,核实它落在 0.06 到 0.10 的预期范围。(vi) 跑 horizon 敏感性 [5, 21, 63, 252] 日,确认 21 到 63 日中等跨度的 sweet spot。(vii) 写明 trial-counter 纪律:如果 (iv) 跑了 200 次超参,写稿时 deflated-Sharpe 校正要按多少 trials 算,deflated-Sharpe 公式是哪个?