第 7 章 · 03 股票收益预测 本节摘要:本节是线性模型的实战——用 Ridge/Lasso 预测股票横截面收益。本节把前两章的工具串联起来:数据准备(从价格构造 RSI/布林带/ATR/MACD 等因子 + 滞后收益 + 行业虚拟变量 + 前向收益标签)、用 statsmodels 做 OLS 统计推断、用 sklearn 的 LinearRegression/Ridge/Lasso 配合第 6 章的 MultipleTimeSeriesCV 做严格的时序交叉验证、用信息系数 IC(每日预测与实际收益的 Spearman 相关)作为核心评估指标。重点演示 Lasso 的稀疏性如何做特征筛选,以及如何比较三个模型在多个调参下的样本外 IC。
本节摘要:本节是线性模型的实战——用 Ridge/Lasso 预测股票横截面收益。本节把前两章的工具串联起来:数据准备(从价格构造 RSI/布林带/ATR/MACD 等因子 + 滞后收益 + 行业虚拟变量 + 前向收益标签)、用 statsmodels 做 OLS 统计推断、用 sklearn 的 LinearRegression/Ridge/Lasso 配合第 6 章的 MultipleTimeSeriesCV 做严格的时序交叉验证、用信息系数 IC(每日预测与实际收益的 Spearman 相关)作为核心评估指标。重点演示 Lasso 的稀疏性如何做特征筛选,以及如何比较三个模型在多个调参下的样本外 IC。
内容来源:原项目
ch07/03_preparing_the_model_data.ipynb、04_statistical_inference_of_stock_returns_with_statsmodels.ipynb、05_predicting_stock_returns_with_linear_regression.ipynb,汉化并套用体系化模板。
⚠️ 风险提示:股票收益预测信噪比极低,样本外 IC 长期 >0.02 已属优秀。任何 IC >0.1 的结果都先怀疑数据泄露(标签未 shift、特征含未来信息)。
阅读完本节,你应当能够:
03_preparing_the_model_data.ipynb 是整条流水线的数据准备环节:
# RSI(相对强弱) def RSI(df, pe=14): returns = df.close.pct_change() gain = returns.where(returns > 0, 0).rolling(pe).mean() loss = -returns.where(returns < 0, 0).rolling(pe).mean() rs = gain / loss return 100 - 100 / (1 + rs) # 布林带、ATR、MACD 类似
这些因子把原始价格序列变换成有预测潜力的特征。第 4 章详讲了因子工程。
关键一步——标签是「未来 N 天收益」,必须 shift:
df['target_1d'] = df.close.pct_change(1).shift(-1) # 明天的收益 df['target_5d'] = df.close.pct_change(5).shift(-5) df['target_10d'] = df.close.pct_change(10).shift(-10) df['target_21d'] = df.close.pct_change(21).shift(-21)
⚠️ 泄露陷阱:如果忘了
.shift(-N),标签就和同期收益对齐,等于「用今天预测今天」,IC 虚高到 0.5+。这是 ML4T 最常见的 bug。
股票收益有极端异常值(财报日 ±30%),会让 OLS/Ridge 的损失被少数点主导。缩尾到 1%~99% 分位:
returns = returns.clip(lower=np.percentile(returns, 1), upper=np.percentile(returns, 99))
04 notebook 用 OLS 做统计推断:解释「哪些因子对收益有显著解释力」。先按 ticker 标准化,消除不同股票价格量纲差异:
by_ticker = data.groupby('ticker').apply(lambda x: (x - x.mean()) / x.std()) model = OLS(endog=by_ticker['target_1d'], exog=add_constant(by_ticker[factor_cols])).fit() print(model.summary())
对 1 天、5 天、10 天、月收益分别跑,典型发现:短期(1d)很难预测(R² < 0.01),长期(月)稍好(R² ≈ 0.02~0.05)。这正是金融「弱信号」的本质——可预测但很弱。
05 notebook 是核心实战。先配置时序 CV(第 6 章):
from utils import MultipleTimeSeriesCV train_period_length = 126 # 半年训练 test_period_length = 21 # 一个月测试 lookahead = 1 # 预测 1 天前向收益 n_splits = 16 cv = MultipleTimeSeriesCV(n_splits=n_splits, test_period_length=test_period_length, lookahead=lookahead, train_period_length=train_period_length)
from sklearn.linear_model import LinearRegression model = LinearRegression() for i, (train_idx, test_idx) in enumerate(cv.split(X), 1): X_train, y_train = X.iloc[train_idx], y.iloc[train_idx] X_test, y_test = X.iloc[test_idx], y.iloc[test_idx] model.fit(X_train, y_train) y_pred = model.predict(X_test) # 算每日 IC(见下节)
金融预测的核心指标不是 R²,而是 IC(Information Coefficient)——每日预测值与实际收益的 Spearman 秩相关:
from scipy.stats import spearmanr # y_test 是 (date, ticker) MultiIndex 的预测值 daily_ic = y_test.groupby('date').apply( lambda x: spearmanr(x['predicted'], x[target]).correlation)
Ridge/Lasso 的核心超参是正则化强度 α(或 sklearn 的 C = 1/α)。扫一组 α,每个 α 在所有 fold 上算 IC:
from sklearn.linear_model import Ridge, Lasso from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline alphas = np.logspace(-5, 5, 11) ridge_scores = [] for alpha in alphas: pipe = Pipeline([('scaler', StandardScaler()), ('model', Ridge(alpha=alpha))]) fold_ics = [] for train_idx, test_idx in cv.split(X): pipe.fit(X.iloc[train_idx], y.iloc[train_idx]) pred = pipe.predict(X.iloc[test_idx]) ic = spearmanr(pred, y.iloc[test_idx]).correlation fold_ics.append(ic) ridge_scores.append([alpha, np.mean(fold_ics)])
💡 Pipeline 必备:Ridge/Lasso 对尺度敏感,必须把 StandardScaler 绑进 Pipeline,让每个 fold 独立标准化(回顾第 6 章)。
Lasso 的最大价值是稀疏——随着 α 增大,越来越多系数归零。画系数路径能看出哪些因子「最后才被压零」(最重要):
from sklearn.linear_model import lasso_path alphas_path, coefs, _ = lasso_path(X_scaled, y, alphas=alphas) # 画 coefs vs log(alpha),看哪些系数坚持到最后
典型发现:滞后收益、MACD、RSI 等少数因子在较大 α 下仍非零,是稳健信号;很多行业虚拟变量很早就归零,贡献微弱。这就是 Lasso 自动特征选择的体现。
把 LinearRegression、Ridge、Lasso 在各自最优 α 下的样本外 IC 对比:
results = pd.DataFrame({ 'LinearRegression': lr_scores, 'Ridge': ridge_best_scores, 'Lasso': lasso_best_scores, }) print(results.groupby('model').ic.describe())
典型结论:
.shift(-N) 防泄露。下一节,我们用 Alphalens 把这些线性模型的预测信号做成标准化的因子评估报告。