第 4 章 · 01 特征工程 本节摘要:本节是「Alpha 因子研究与特征工程」的开篇方法论章,讲怎么把原始 OHLCV 与基本面数据系统化地变成「能喂给 ML 模型的特征」。原项目 用 Quandl 美股 2000-2018 年的日频数据,演示了一套完整的特征工程流水线:从月度收益序列的构造、动量因子的计算、滚动 Fama-French 因子暴露、滞后收益作为输入、到持有期收益作为预测目标,再到 IPO 年限(年龄)、市值等横截面特征,最后用 one-hot 处理分类变量并存盘供后续章节复用。读完本节,你能独立构造一套完整的「特征 + 标签」数据集,作为后续任何监督模型的输入。 内容来源:原项目 ,汉化并套用体系化模板。
本节摘要:本节是「Alpha 因子研究与特征工程」的开篇方法论章,讲怎么把原始 OHLCV 与基本面数据系统化地变成「能喂给 ML 模型的特征」。原项目
04_alpha_factor_research/01_feature_engineering.ipynb用 Quandl 美股 2000-2018 年的日频数据,演示了一套完整的特征工程流水线:从月度收益序列的构造、动量因子的计算、滚动 Fama-French 因子暴露、滞后收益作为输入、到持有期收益作为预测目标,再到 IPO 年限(年龄)、市值等横截面特征,最后用 one-hot 处理分类变量并存盘供后续章节复用。读完本节,你能独立构造一套完整的「特征 + 标签」数据集,作为后续任何监督模型的输入。
内容来源:原项目
04_alpha_factor_research/01_feature_engineering.ipynb,汉化并套用体系化模板。
⚠️ 风险提示:特征工程是数据泄露的高发区——任何用到「未来」信息的变换都会污染回测;务必严守「当前时刻只用历史」,所有滚动/位移操作都要显式避免未来函数。
阅读完本节,你应当能够:
pandas 的 rolling/shift/pct_change 构造月度收益与动量。pyfinance.PandasRollingOLS 计算滚动 Fama-French 因子暴露。原书第 4 章 README 开宗明义:「如果你熟悉 ML,就知道特征工程是成功预测的关键;在交易领域至少同样重要。」(feature engineering is a key ingredient for successful predictions. It matters at least as much in the trading domain.)
金融机器学习的瓶颈几乎从来不是模型,而是特征。模型再先进,输入是垃圾就只能输出垃圾。本节的目标是把第 2~3 章拿到的原始数据(价格、财报、元数据)加工成一张「**行=日期×标的,列=特征**」的整洁表(称为 feature_engineering.ipynb 的输出),后续第 7 章线性模型、第 9 章树模型都直接用它。
notebook 一开始从 assets.h5(第 2 章存储基准推荐的方式)读取 Quandl 2000-2018 年的复权收盘价与股票元数据:
DATA_STORE = '../data/assets.h5' with pd.HDFStore(DATA_STORE) as store: prices = (store['quandl/wiki/prices'] .loc[idx['2000':'2018', :], 'adj_close'] .unstack('ticker')) stocks = store['us_equities/stocks'].loc[:, ['marketcap', 'ipoyear', 'sector']]
要点:
pd.IndexSlice 对 MultiIndex 做切片。unstack 成宽表(行=日期,列=ticker),适合后续按列算指标。marketcap(市值)、ipoyear(上市年)、sector(行业),后两个是横截面特征。💡 核心心法:先把数据整理成「日期 × 标的」的整齐宽表或长表,后续所有特征工程都在这张表上做。混乱的索引会让每一步变换都痛苦。
日频数据噪声大、训练慢,notebook 把它重采样为月频:
monthly_prices = prices.resample('M').last()
然后计算不同周期的历史收益作为动量因子:
# 不同回看周期的收益率 for t in [1, 2, 3, 6, 9, 12]: out[out.columns] = monthly_prices.pct_change(t)
关键后处理:
.clip() 把 1% 与 99% 分位数之外的极端值截断,防离群点主导。动量因子的常见构造方式:用 3~12 月历史收益之差,或近期月收益与长期均值之差。
⚠️ 警告:
pct_change(n)看的是「过去 n 期」,这是合法的历史信息;但如果你误用pct_change(-n)(未来 n 期),就是数据泄露。所有shift/pct_change都要确认方向。
Fama-French 五因子(市场、规模、价值、盈利、投资)是经典的风险解释因子。把股票收益对这五因子做滚动回归,得到的 β 就是该股票随时间变化的因子暴露,可作为特征:
from pyfinance.ols import PandasRollingOLS factor_data = web.DataReader('F-F_Research_Data_5_Factors_2x3', 'famafrench', start='2000')[0].drop('RF', axis=1) factor_data.index = factor_data.index.to_timestamp() # 对每只股票做滚动 24 个月 OLS betas = PandasRollingOLS(window=24, min_periods=12).fit( y=monthly_returns, x=factor_data)
缺失的 β(回归窗口不足)用均值填充,避免丢样本。
这是本节最关键也最容易出错的部分:
| 角色 | 怎么构造 | 含义 |
|---|---|---|
| 特征(滞后收益) | 历史 .shift(+n) 把过去收益对齐到当前 |
t 时刻「能用」的信息 |
| 标签(持有期收益) | 未来收益 .shift(-h) 对齐到当前 |
t 时刻入场、持有 h 期的结果 |
💡 核心心法:同一行 DataFrame 里,特征必须是 t 之前已发生的信息,标签是 t 之后才发生的结果。任何违反这条规则的行都构成数据泄露。
shift(+n)用于特征,shift(-h)用于标签,符号别搞反。
notebook 还构造了两类不随时间剧烈变化的横截面特征:
# 按月分十进制桶,避免未来市值泄露 size = ... # 调整后的历史市值 size_decile = size.groupby('date').apply(lambda x: pd.qcut(x, 10, labels=False, duplicates='drop'))
⚠️ 警告:市值与当前价格绑定,直接用会泄露未来价格信息。必须构造调整因子让历史市值反映当时价格水平,这是常被忽略的泄露点。
最后,对 sector 这类分类变量做 one-hot 编码:
dummy_sectors = pd.get_dummies(stocks['sector'], prefix='sector', drop_first=True)
整理完后存盘,供第 7 章起复用:
with pd.HDFStore(DATA_STORE) as store: store['engineered_features'] = data
PandasRollingOLS 算随时间变化的风险暴露。shift(+n)(过去),标签用 shift(-h)(未来),符号别搞反。下一节,我们看 TA-Lib 技术指标——除了用 pandas 手搓特征,还可以用 TA-Lib 库直接调几十种经典技术指标。