第 9 章 · 02 随机森林调参与日本股票多空 本节摘要:本节把上一节的 Bagging 升级成随机森林,并落到一个完整的多空策略——日本大盘股。随机森林在 Bagging 的「样本随机」之上再加「特征随机」:每个分裂点只考虑部分特征,强制让不同的树走不同的切分路径,把树与树的相关性压下来,方差随之进一步下降。本节讲清四件事:随机森林相对 Bagging 的增量价值与 OOB(袋外)评估;如何在 Quandl 美股数据上用 + 调参;如何用 Stooq 日本股票数据做特征工程(动量、波动、TA-Lib 指标);以及如何用 LightGBM 生成多空信号,用 Zipline 回测一个市场中性策略,并用 Alphalens 看信号质量。
本节摘要:本节把上一节的 Bagging 升级成随机森林,并落到一个完整的多空策略——日本大盘股。随机森林在 Bagging 的「样本随机」之上再加「特征随机」:每个分裂点只考虑部分特征,强制让不同的树走不同的切分路径,把树与树的相关性压下来,方差随之进一步下降。本节讲清四件事:随机森林相对 Bagging 的增量价值与 OOB(袋外)评估;如何在 Quandl 美股数据上用
GridSearchCV+MultipleTimeSeriesCV调参;如何用 Stooq 日本股票数据做特征工程(动量、波动、TA-Lib 指标);以及如何用 LightGBM 生成多空信号,用 Zipline 回测一个市场中性策略,并用 Alphalens 看信号质量。读完本节,你能跑通一个从特征工程到绩效报告的非线性多空策略。
内容来源:原项目
11_decision_trees_random_forests/03_random_forest_tuning.ipynb、04_japanese_equity_features.ipynb、05_random_forest_return_signals.ipynb、06_alphalens_signals_quality.ipynb、07_backtesting_with_zipline.ipynb,汉化并套用体系化模板。
⚠️ 学习提示:树模型易过拟合,样本外 IC 与夏普普遍衰减。这里的多空回测仅作流程示范,直接照搬到实盘几乎一定亏损。
阅读完本节,你应当能够:
GridSearchCV + 时序 CV 搜索随机森林超参。随机森林在 Bagging 的两处随机化之上,再加第三处:
| 随机化来源 | Bagging | 随机森林 |
|---|---|---|
| 行抽样(bootstrap) | 有 | 有 |
| 树之间独立性 | 部分 | 强制 |
| 每个分裂点特征随机 | 无 | 有 |
具体做法:在每个节点分裂时,只从全部 p 个特征里随机选 m 个(m \ll p,典型 m = \sqrt{p}),只在这 m 个里找最优切分。这强制让「最强特征」不会出现在每棵树的每个分裂点——不同的树被迫用不同的次强特征,树与树的相关性显著下降。
💡 核心心法:随机森林的关键不在「训更多树」,而在「让树各想各的」。如果所有树都在同一个最强特征上做同一个切分,再平均也没用——降不了多少方差。特征随机化就是打破这种「思维趋同」。
bootstrap 抽样有放回,每棵树大约 37% 的训练样本没被抽中——这些叫「袋外(OOB)样本」。对每棵树,用它没见过的 OOB 样本评估它,再把所有树的 OOB 评估聚合,就得到一个不依赖额外验证集的泛化估计。这是随机森林相对普通 Bagging 的额外红利,尤其样本稀缺时极其有用。
notebook 03_random_forest_tuning.ipynb 用 Quandl 月度数据:
with pd.HDFStore('data.h5') as store: data = store['us/equities/monthly'] y = data.target y_binary = (y > 0).astype(int) X = pd.get_dummies(data.drop('target', axis=1))
随后用 GridSearchCV 配合自定义的 MultipleTimeSeriesCV(时序交叉验证)搜索 n_estimators、max_depth、max_features 等关键超参:
from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import GridSearchCV rf = RandomForestRegressor(random_state=42, n_jobs=-1, oob_score=True) param_grid = {'n_estimators': [50, 100, 250], 'max_depth': [None, 10, 20], 'max_features': ['sqrt', 'log2', None]} grid = GridSearchCV(rf, param_grid, cv=mts_cv, scoring=ic_scorer)
| 超参 | 调参方向 |
|---|---|
n_estimators |
越多越稳,边际收益递减,通常 100~500 够用 |
max_features |
越小越随机,降方差但提偏差,sqrt 是经典默认 |
max_depth |
限制深度可抗过拟合,但太浅又欠拟合 |
min_samples_leaf |
调大可抗过拟合 |
oob_score |
开启,获取免费泛化估计 |
⚠️ 时序 CV 必须:金融数据有强时间结构,普通 K-fold 会把未来泄漏到训练集。
MultipleTimeSeriesCV保证训练段永远在测试段之前,且 purge 掉标签重叠的样本——这是评估金融模型不可省的一步。
notebook 04_japanese_equity_features.ipynb 用 Stooq 提供的日本东证股票数据(2010-2019):
prices = (pd.read_hdf(DATA_DIR / 'assets.h5', 'stooq/jp/tse/stocks/prices') .loc[idx[:, '2010': '2019'], :])
特征工程围绕「动量 + 波动 + 技术指标」三类:
return_1m、return_3m、return_6m、return_12m(经典动量)。rank(axis=1) 把绝对值转成横截面位次,消除规模差异。构造好后保存为 model_data,供后续建模直接读取。
💡 为什么强调横截面 rank:日本股票之间规模差异大(丰田和小盘股市值差几十倍),用绝对收益做特征会让大票主导模型。
rank把所有票拉到「1 到 N」的同尺度,这是多空选股的标准预处理。
虽然章节名是「随机森林」,但 notebook 05_random_forest_return_signals.ipynb 实际用 LightGBM(下一章详讲)做收益预测——它能高效处理类别特征,逻辑与 sklearn 的随机森林高度相似。流程:
model_data,划分特征与多个前瞻标签(1/5/21 日)。MultipleTimeSeriesCV 滚动训练,记录每个 fold 的 IC(Spearman 秩相关)。import lightgbm as lgb from scipy.stats import spearmanr # 每折训练后,在测试段算 IC ic = spearmanr(y_test, y_pred).correlation
notebook 06_alphalens_signals_quality.ipynb 用 Alphalens 出信号分组的收益分布、信息系数衰减、换手率等标准 tearsheet:
from alphalens.utils import get_clean_factor_and_forward_returns from alphalens.tears import create_full_tear_sheet factor_data = get_clean_factor_and_forward_returns(factor, prices) create_full_tear_sheet(factor_data)
这能在跑 Zipline 回测之前,先从「信号有没有分组单调性」「IC 在哪些前瞻期最强」「换手是否过高」几个维度筛掉低质信号——比直接看回测夏普更早暴露问题。
notebook 07_backtesting_with_zipline.ipynb 把信号接进 Zipline,做多预测最高的若干只、做空最低的若干只,目标是市场中性:
from zipline import run_algorithm from zipline.api import (attach_pipeline, pipeline_output, schedule_function, date_rules, time_rules, order_target_percent) def initialize(context): attach_pipeline(make_pipeline(), 'signals') def rebalance(context, data): signals = pipeline_output('signals') longs = signals[signals.long_short > 0].index # 等权分配,做多 longs,做空 shorts for stock in longs: order_target_percent(stock, target_weight)
最终用 run_algorithm() 跑完整个回测期,输出 pyfolio 绩效。整套流程串起第 8 章学到的回测基础设施,加上本章的非线性信号——这就是 ML4T 端到端工作流的一次完整落地。
⚠️ 回测衰减是常态:你大概率会看到样本外夏普明显低于样本内,这是树模型在金融数据上的典型表现——信号弱、噪声大、易过拟合。把这次回测当作「流程练习」,而非「可上线策略」。
GridSearchCV + MultipleTimeSeriesCV,严禁普通 K-fold,会泄漏未来。下一节,我们看 GBM 基线——从 AdaBoost 到梯度提升,理解「串行拟合负梯度」的另一类集成思路。