machine-learning-for-trading 案例实战:S&P 500 股票 + 期权分析——从点内特征工程到周度组合的全流程研究管线
【免费下载链接】machine-learning-for-tradingCode for Machine Learning for Trading, 3rd edition — from data sourcing to live execution.项目地址: https://gitcode.com/GitHub_Trending/ma/machine-learning-for-trading
本案例(sp500_equity_option_analytics)是 machine-learning-for-trading 仓库中一个端到端的策略研究示范:用 633 只 S&P 500 成分股的期权隐含波动率信息(IV 水平、偏斜、期限结构与方差风险溢价)对股票未来 5 日收益进行横截面排序,并最终在股票市场上持有排序靠前的股票。读完本文,你将掌握该案例的完整研究契约(决策时点、标签体系、特征族、评估协议)、21 个阶段的流水线编排方式、基于内容寻址注册表的可复现实验管理,以及它对"期权信息能否穿越点内特征工程、周度组合构建、成本、风控与 regime 变化"这一核心问题的严谨证据边界。
案例概览与数据集画像
该案例的交易逻辑非常明确:读取期权的"观点",在股票市场"下注"——每周根据期权曲面摘要排序 S&P 500 成分股,持有排序靠前的股票,从不持有期权本身。研究区间为 2017 至 2021 年,输入数据是 AlgoSeek 的 S&P 500 日线以及物化后的日频期权曲面。以下是 case_studies/sp500_equity_option_analytics/README.md 中给出的数据画像:
| 属性 | 取值 |
|---|---|
| 资产类别 | S&P 500 股票 + 期权衍生预测因子 |
| 频率 | 日频输入;每周五收盘决策 |
| 历史区间 | 2017–2021 |
| 标的池 | 633 只具备挂牌期权覆盖的股票 |
| 输入 | AlgoSeek S&P 500 日线 + 物化的日频期权曲面 |
| 主标签 | fwd_ret_5d |
| 评估 | 两个 walk-forward fold;10 个交易日的 embargo;2021 年 holdout |
| 执行 | 周五收盘决策,下周一开盘成交;期权特征滞后一天 |
| 成本模型 | 配置中点价 13 bps 往返;0–50 bps 压力网格 |
| 当前证据 | v3.1 验证载体:NLinear、score weighted、top 10、5% 尾随止损 |
一个值得注意的细节是:纠正后的 v3.1 验证 Sharpe 为 2.088,95% 置信区间为 [1.005, 3.117],但由于 2021 年 holdout 已在旧的 IPCA lineage 上被观测过,与 NLinear 匹配的 holdout 没有重新运行。因此当前 notebook 只呈现验证证据,明确将样本外有效性标记为未决(label out-of-sample efficacy unresolved),不做任何部署声明;与书对齐的 v3.0 记录被保留,两个结果版本不混用。这正是该仓库在方法论纪律上的核心态度。
研究问题:信息是否穿越完整管线
这个案例最独特的问题不是"期权是否包含信息",而是"这些信息能否在点内特征工程、周度组合构建、成本、风控和 regime 变化之后依然存活"。决策协议在 config/setup.yaml 中声明,关键设计包括:
- 决策节奏:
decision.cadence: weekly_friday_close,快照时刻为friday_16:00_et,执行延迟为monday_open; - 特征滞后:
iv_feature_lag: 1_day——期权曲面摘要在当日收盘才盖章,只能在下一次决策时读取,杜绝前视; - 按标签差异化节奏:
cadence_by_label将两个 10 日标签(fwd_ret_10d、fwd_dir_10d)调整为双周(biweekly)网格。原因是 10 日标签在周度网格上两周后才结算,新仓会与前一个仍在持有期内的仓位重叠,重叠的正是被度量的对象本身; - 映射规则:
mapping.class: long_only_rank_and_rebalance,按 IV 信号排序、等权持有,不做空、不做期权。
特征体系:点内特征注册表
setup.yaml中的features块是一份特征规格注册表,而非 notebook 内的局部变量。注释明确说明了这样设计的原因:"特征是本研究案例的特征集声明,只有 notebook 持有的声明无法被测试、后续阶段或任何询问变更的人读取。"
曲面合约选择(由data/equities/market/sp500/materialize_options.py固定):
dte_buckets:7d: [5, 10]、30d: [25, 35]、90d: [80, 110]delta_targets:atm: 0.50、25d: 0.25、10d: 0.10
窗口配置:IV z-score 用 63/252 日窗口、IV 百分位 252 日、IV 动量 5/21 日、偏斜与期限 z-score 63 日、已实现波动 20/63 日、Garman-Klass 21 日、动量在 5/21/63/126/252 五个 horizon 上计算、skip-month 动量取 t-252 到 t-21、风险调整动量设置 0.01 的年度化波动地板(防止分母趋零导致单行主导截面百分位)、IV 前向填充上限 5 个交易日。
排名列映射(features.ranked):iv_30_atm → iv_rank、skew_rr_30_25d → skew_rank、ivrv_spread → vrp_rank、mom_21d → mom_21d_rank、mom_63d → mom_63d_rank、rv_20 → rv_rank、iv_mom_21d → iv_mom_rank、d_iv_30_atm → d_iv_rank,命名显式映射而非后缀规则,因为后置阶段按这些名字选择列。
八大特征族(features.families)——每个族都同时声明了hypothesis(假设)、inputs(输入)、lookback、lag、representation(表示形式)与failure_mode(失效模式),这是本案例最有价值的工程实践之一:
| 特征族 | 模式 | 滞后 | 表示形式 | 失效模式 |
|---|---|---|---|---|
横截面排名(*_rank) | 252 | 1 | 日内在 (0,100) 的百分位 | 薄截面使百分位退化为少数名字的粗排序 |
隐含波动率水平(iv_30_atm等) | 1 | 1 | 年化 IV,方差点 | 报价稀薄的名字由单个陈旧合约决定水平 |
隐含波动率动态(d_iv_30_atm、iv_mom_*、z-score/百分位) | 252 | 1 | 一阶差分、多期变化、尾随 z-score 与百分位 | z-score 在自身离散度趋零时无界 |
偏斜与期限结构(skew_*、term_*) | 63 | 1 | 曲面点之间差与比、尾随 z-score | 远端桶读取的是插值而非报价的合约 |
方差风险溢价(ivrv_spread、vrp_z_*) | 63 | 1 | 波动率点差与尾随 z-score | 前瞻一个月与后顾一个月在事件附近分歧 |
已实现波动(rv_20、gk_vol_*等) | 63 | 0 | 年化标准差、极差估计、三阶矩 | close-to-close 漏掉隔夜跳空 |
股票动量(mom_*) | 252 | 0 | 五 horizon 简单收益、skip-month 与波动缩放变体 | 最近一个月反转,skip-month 动量将其剔除 |
曲面质量(spread_atm_*、qc_converged_share) | 1 | 1 | 相对点差、收敛点占比 | 度量报价质量而非流动性,到期附近二者背离 |
注意滞后语义:每个期权衍生族滞后一个交易日(曲面摘要按收盘盖章),每个股票价格族滞后为零(收盘即决策快照)。
完整流水线:21 个阶段的贪心漏斗
整个研究被编排为 21 个 notebook(每个都有对应的.py脚本),按阶段严格串行。核心原则是:不能静默跳过某个模型族,也不能从部分基线上启动下游阶段——只有所有模型预测和所有等权基线都存在后,贪心漏斗才有效。
| 阶段 | Notebook | 章节 | 教学点 | 写出物 |
|---|---|---|---|---|
| 可行性 | 01_feasibility_analysis | 6 | 检验期权覆盖、周度节奏,以及股票交易成本是否为研究留出空间 | 无(证据留在 notebook 内) |
| 标签 | 02_labels | 7 | 构建 5/10 日收益、风险调整与方向标签,含 walk-forward 边界 | labels/fwd_ret_5d.parquet等 5 个标签文件 |
| 金融特征 | 03_financial_features | 8 | 将滞后 IV 曲面与已实现波动、动量、流动性特征连接 | features/financial.parquet |
| 时变特征 | 04_model_based_features | 9 | 产出仅前视的 GJR-GARCH 波动特征,并记录固定的单起点特征 vintage | features/model_based.parquet |
| 评估 | 05_evaluation | 7–9 | 审计覆盖度、陈旧度、日频 IC 与 HAC 不确定性 | evaluation/triage_ledger.parquet、evaluation/ic_timeseries.parquet |
| 线性模型 | 06_linear | 11 | 在标签面板上建立正则化线性与分类基线 | run_log/registry.db中的训练与预测记录 |
| 梯度提升 | 07_gbm | 12 | 训练 LightGBM 配置并记录 fold 完整验证预测 | boosters、learning_curves.parquet、fold_metrics.parquet |
| 表格深度学习 | 08_tabular_dl | 12 | 在股票+期权联合特征面板上评估 TabM 集成 | run_log/training/tabular_dl/下的 checkpoint |
| LSTM | 09_dl_lstm | 13 | 检验循环序列结构是否优于点内特征 | run_log/training/deep_learning/下的 checkpoint |
| PatchTST | 10_dl_patchtst | 13 | 检验多尺度 patch 注意力与 checkpoint 稳定性 | 同上 |
| 潜因子索引 | 11_latent_factors | 14 | 比较潜因子家族并引导读者到各实现 | 无(只读注册表) |
| PCA | 11a_pca | 14 | 在每个训练 fold 内对持久面板拟合 PCA | 训练与预测记录 |
| IPCA | 11b_ipca | 14 | 在 ragged 面板上估计特征条件因子 | 训练与预测记录 |
| 条件自编码器 | 11c_conditional_autoencoder | 14 | 学习非线性条件因子暴露,不跨越 fold 边界 | 训练与预测记录 |
| 随机贴现因子 | 11d_stochastic_discount_factor | 14 | 估计神经 SDF 并比较 checkpoint 稳定性 | 训练与预测记录 |
| 监督自编码器 | 11e_supervised_autoencoder | 14 | 学习收益监督潜因子并按 checkpoint 报告不确定性 | 训练与预测记录 |
| 因果 DML | 12_causal_dml | 15 | 用 walk-forward DML 与面板稳健推断估计ivrv_spread效应 | 注册表causal_runs中的一行 |
| 模型分析 | 13_model_analysis | 11–15 | 用带 HAC 区间的日频 IC 与特征溯源比较全覆盖家族 | 无(只读注册表) |
| 等权基线 | 14_backtest | 16 | 运行等权 top-k 基线并应用覆盖感知选择 | run_log/backtest/{hash}/下daily_returns.parquet、weights.parquet、trades.parquet、fills.parquet、equity.parquet、portfolio_state.parquet、spec.json |
| 组合构建 | 15_portfolio_management | 17 | 在十个推进配置上测试五种替代分配器 | 每种分配方法一个回测,布局同上 |
| 风控 | 16_risk_management | 19 | 比较 14 个预声明固定风控,含配对收益不确定性 | 每个 overlay 变体一个回测 |
| 成本 | 17_costs | 18 | 在精确 17 点成本曲面上重放最佳风险阶段配置 | 每个成本水平一个回测 |
| Holdout 预测 | 18_holdout_predictions | 20 | 用 2021 年前全部历史重拟合选中配置并预测 holdout | 一个训练运行 + 一个预测集 |
| Holdout 回测 | 19_holdout_backtest | 20 | 原样运行选中策略于 holdout 预测 | stage='holdout'的一个回测 |
| 策略评估 | 20_strategy_analysis | 20 | 汇总本案例已确立的内容,声明 holdout 支持哪些主张 | 无(只读注册表) |
漏斗各阶段的工程细节
从源码中可以确认各阶段的推进逻辑(research_workflow.py 是模型工作流辅助模块):
published_labels()从setup.yaml读出主标签与变体;model_request_catalog()构建"标签 × 模型配置"的声明式请求目录,空标签列表遵循"表示全部已发布标签"的约定,而空配置列表则被拒绝(防止静默扩宽);require_complete_canonical_requests()在 canonical 执行模式下拒绝不完整的声明请求面——不能悄悄跳过某个模型族;run_model_population()在拟合前快照官方群体(OfficialPopulation),拟合后按内容寻址的预测 hash 做精确比对,缺一不可;任何移动训练身份的变更都必须显式声明supersedes。
模型菜单(config/training/fwd_ret_5d.yaml)展示了每个标签的配置清单:线性族含 OLS、8 档 ridge(alpha 0.001 到 1e7)、9 档 lasso 与 9 档 elastic-net;GBM 含default_mse/mae/huber与 5 档 leaves 数 × 3 种损失的组合;深度学习含nlinear、lstm_h64、patchtst;表格 DL 含tabm_s/m/l;潜因子含pca/ipca/cae/sdf/sae;因果含dml。
标签体系(02_labels.py)中有几个容易被忽略的坑:
- 标签采用 next-open-to-close 约定
r = C_{t+h} / O_{t+1} - 1,而非书本 7.2 节的 close-to-close——锚定在 t 收盘的标签会把信号与首个可成交价格之间的隔夜跳空记入策略收益; - 方向标签的
when(ret > 0)在 Polars 中 null 谓词不为真,若不加is_not_null()守卫,会在每个证券最后一周的每行写入"自信的 0"且事后drop_nulls无法发现; - 每个标签都做窗口有效性断言与逐行归因对账(尾部、窗口空洞、价格缺失三类原因必须恰好覆盖所有未标注行);
- 有效性被切在"标签结算日"而非"观测日":观测于 holdout 之前、结算于其内的行是 holdout 行。
回测引擎协议(config/backtest/base.yaml):execution_price: open+execution_mode: next_bar,佣金 0.039% 百分比模型、滑点 0.026%,NYSE 日频日历。这与"周五收盘形成信号、下一 bar(周一开盘)成交"的事件顺序一致,防止周五收盘特征获得同 bar 成交。引擎层执行默认值(如initial_cash: 1_000_000、share_type: integer)统一从setup.yaml读取,任何改动都会使既有 backtest_hash 失效。
运行方式与数据获取
在仓库根目录、锁定环境下运行整条流水线。需要设置ML4T_DATA_PATH指向包含equities/market/sp500/daily_bars.parquet与equities/market/sp500/options_surface_daily.parquet的目录。值得强调的是:股票日线随仓库分发在data/equities/market/sp500/daily_bars.parquet,经 AlgoSeek 许可再分发,无需任何账号、密钥或许可申请;若在出版物中使用,请注明数据来源为 AlgoSeek。期权曲面加载器在物化研究文件可用时提供该文件,缺失数据会报错并附带获取说明。
uv sync --frozen for notebook in \ 01_feasibility_analysis 02_labels 03_financial_features 04_model_based_features \ 05_evaluation 06_linear 07_gbm 08_tabular_dl 09_dl_lstm 10_dl_patchtst \ 11_latent_factors 11a_pca 11b_ipca 11c_conditional_autoencoder \ 11d_stochastic_discount_factor 11e_supervised_autoencoder 12_causal_dml \ 13_model_analysis 14_backtest 15_portfolio_management 16_risk_management \ 17_costs 18_holdout_predictions 19_holdout_backtest 20_strategy_analysis do uv run python "case_studies/sp500_equity_option_analytics/${notebook}.py" done一个完整的生产级运行在 CUDA 机器上是数小时级工作量,深度学习和潜因子训练占主导;CPU 执行受支持但明显更慢。当仓库自带的注册表与产物存在时,已完成 hash 会被复用,notebook 会报告每个缓存命中。
结果的唯一事实来源是run_log/registry.db,内容寻址产物位于run_log/training/、run_log/predictions/、run_log/backtest/下;遗留的results/*.json文件已不再使用。每个产物还配套.digest.json边车文件记录值摘要与输入数据摘要,用于检测陈旧读取。
等权基线基准
benchmark/目录给出了等权基准的量化参考(benchmark/fwd_ret_5d.json,方法为daily_cross_sectional_mean_close_pct_change,年化周期 252,池内 550 只股票):
- 总体:Sharpe 0.883,CAGR 20.8%,波动 25.0%(753 期);
- 验证窗口(2019-01-08 至 2020-12-31):Sharpe 0.685,CAGR 17.0%,波动 29.1%(501 期);
- Holdout(2021-01-04 至 2021-12-31):Sharpe 1.913,CAGR 28.9%,波动 13.8%(252 期)。
该基准说明等权、无预测的"买入持有式"基线本身在这段样本上就不弱,任何模型/组合改进都必须以超越它为门槛。
可行性阶段的关键测量
01_feasibility_analysis.py 不拟合任何模型,只回答三个问题:决策日的标的池是否存在、典型股价波动是否大于交易成本、历史是否足够诚实评估。几个可复现的结论:
- 未决值处理:供应商把求解失败的 IV 记为负数占位符,直接 drop 会留在排序里把该股打到最底。加载器统一将其转为缺失值,并断言"存在的 IV 必为正、每股每会话至多一行";
- 标的池按挂牌日历而非流动性规则波动:每月第三个周五的月度到期在窗口内时,可排序股票超过 500;仅有周度到期时降到 210–260 左右,远高于 book 需要的 20 只下限;
- 成本:13 bps 的百分比往返在按每股计价时对应 0.04 bps(最贵股票)到 619 bps(最便宜股票)——跨三个数量级,这正是"按百分比"成为主线模型、"按每股"仅作对照的原因;
- 移动 vs 成本:5 日绝对移动中位数 209 bps,是 13 bps 往返的 16 倍,0.965 的移动超过往返成本;
- 排序持久性:IV 排序与一周后的自身排序相关系数 0.92,八周后仍有 0.75——支持周度而非日度/月度再平衡;
- walk-forward:
evaluation.n_splits: 2,每 fold 训练 2 年、验证 1 年,fold 边界间有 10 个会话的 purge(labels.buffer: 10D对 5 日 horizon,是独立所需的两倍),断言每个 purge 缺口与声明一致、验证窗口不触及 2021。
模型化与因果设计
config/setup.yaml 的modeling块声明了各家族的训练契约:
- GBM:仅 LightGBM,
max_bin: 255(显式声明,避免把 CPU 默认 255 量化误用成 GPU 的 63 档粗分箱); - 潜因子:
device: cuda显式声明并参与 hash 计算,防止训练身份变成宿主机属性;IPCA 配置max_iter: 10000、factor_ridge: 0.01、gamma_ridge: 0.01;SDF 的 checkpoint 周期为 [256, 512, 768, 1024];SAE 显式声明batch_size: 10000——因为库默认无批处理会把整个训练窗口(约 25 万行)当作一个 batch,24 GB 显存放不下; - GJR-GARCH 特征(
model_based块):burnin: 252个会话(接近 fold 0 前的 250 会话 run-up,若取 504 会把覆盖率从 76.5% 压到 54.7%)、每 21 会话重估一次、规格为mean: Constant, vol: GARCH, p:1, o:1, q:1, dist: Normal——o: 1是本案例相对共享 GARCH(1,1)-Normal 默认的声明偏差,理由是基于数据的:单名股票期权报价围绕"负收益比同幅正收益更推高下一期方差"的不对称性展开; - 因果 DML(
causal块):处理变量为ivrv_spread,treatment_window: 20由构造直接推导(iv_30_atm - rv_20),placebo 块必须覆盖该窗口才能保留序列依赖;混杂因子为rv_20、mom_21d、skew_rr_30_25d,方法walk_forward_dml,结果写入注册表causal_runs。
组合、风控与成本压力
setup.yaml的backtest.sweep定义了漏斗各阶段的扫描广度:
- top_n 递进:signal 阶段取全部预测(等权基线),allocation 阶段取按等权基线 Sharpe 排序的前 10 个模型配置,cost 与 risk 阶段每标签取前 1;
- top_k 网格:五个标签统一为
[5, 10, 20]; - 分配器:
score_weighted、inverse_vol、risk_parity、mvo_ledoit_wolf(显式 126 bar lookback,保证 top_k=20 时 N/K ≥ 2.5,Ledoit-Wolf 收缩不塌缩为恒等目标)、hrp、conformal_weighted——其中 conformal 分配器按预测区间宽度而非收益矩定仓,是唯一读取模型自身不确定性的分配器,同时检验13_model_analysis度量的校准是否好到足以用于定仓; - 成本网格:
cost_grid_bps: [0, 1, 2, 3, 5, 7, 10, 15, 20, 30, 50](11 点)加上配套的每股半价差网格[0.0, 0.005, 0.01, 0.025, 0.05, 0.10]美元; - 14 个预声明风控:3 档止损(3/5/10/15%)、6 档尾随止损(1/2/3/5/10/15/20%)、3 档时间退出(10/20/40 bars);
- 再平衡门槛:
min_weight_change: 0.005且min_trade_value: 100.0时跳过再平衡;基准画像禁用门槛以保证 1/N 全池再平衡。
标签的有效样本量与基线 IC
02_labels.py 对重叠窗口做了量化:周度标签 503,079 行开发行对应 101,099 个有效观测(比率 0.2010,理论完全重叠参考值 0.2000);双周标签 500,062 行对应 50,548(比率 0.1011)。面板自相关从 lag-1 的 0.772 衰减到 lag-5 的 -0.051。风险调整标签把周度标签的峰度从 16.48 降到 7.16。未加工信号(滞后一日的 30 日 ATM IV)对周度标签的平均 IC 为 -0.0072,HAC t 统计量 -0.50(p=0.614)——基线无法与零区分,这正是后续特征必须超越的地板。
策略评估的纪律与限制
20_strategy_analysis.py 作为最后阶段只读注册表、不创建任何东西,其学习目标包括:从"配置的、全覆盖的注册表行"而非"全局最大 Sharpe"重建当前配置;把阶段推进、成本存活、配对风险不确定性与选择调整视为不同诊断;当方法论修复在 holdout 已被观测后改变验证配置时强制"一次 holdout 使用";产出区分验证证据与未决样本外有效性的出版级评估。
该案例明确声明的人口范围限制是:使用**当前成分股名单(current-constituent roster)**而非点内 S&P 500 成员资格,历史结果描述的是回顾性名单,不确立指数成员流程或前瞻性 S&P 500 群体的表现。此外,从 Ch11 起每个模型都在期权衍生列与价格列上联合拟合,报告的只是"同时拿到两类的模型"的贡献,无法回答"期权信息是否比免费的价格历史更有价值"——这需要一次剔除期权列的对照拟合,本案例未运行,13_model_analysis也记录了这一限制。
总结:可复用的工程模式
从本案例中可以提炼出几条适用于任何量化研究项目的模式:
- 单点事实来源(single source of truth):标签、特征窗口、执行默认值、成本模型全部声明在 config/setup.yaml,notebook 只读取不重复,杜绝"两处拷贝漂移";
- 内容寻址 + 显式继承:所有训练、预测、回测产物以 hash 组织在
run_log/下,registry.db是唯一事实来源,任何配置变更使 hash 失效,新旧结果版本不混用; - 点内纪律内建为断言:特征滞后、按
sec_id而非 ticker 界定的实体、按市场会话网格而非行数的窗口、求解失败占位符的加载器级归一化,全部以代码断言而非文档约定落地; - 评估诚实性:purge 宽度、fold 数、holdout 边界、HAC 标准误、FDR 多重检验、有效样本量、选择调整与"一次 holdout 使用"共同构成可辩护的证据链;
- 数据获取零门槛:经许可再分发的 AlgoSeek 日线随仓库提供,配合 data 目录 的加载器即可从
01_feasibility_analysis一路跑到20_strategy_analysis。
【免费下载链接】machine-learning-for-tradingCode for Machine Learning for Trading, 3rd edition — from data sourcing to live execution.项目地址: https://gitcode.com/GitHub_Trending/ma/machine-learning-for-trading
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考