摘要: 本文是“多因子轮动”系列的第五篇。前面四篇我们完成了从因子构建到回测对比的全流程,并使用等权和经验权重合成了综合分数。但这些固定权重无法适应市场环境的变化。今天,我们将引入机器学习中的岭回归模型,让它根据历史数据动态学习各因子的最优权重。我们将详细讲解如何使用滚动窗口训练模型、如何避免未来函数、以及如何将模型输出的权重嵌入到现有的多因子轮动框架中。最后,我们会诚实讨论机器学习在实盘中的局限与风险。读完本文,你将拥有一个能够自适应市场的多因子轮动策略。
大家好,我是你们的老朋友。
前面四篇文章,我们完整地走完了多因子轮动的全流程。在第四篇的回测对比中,我们采用等权或经验加权来合成综合分数,取得了不错的成绩。但有一个问题始终悬而未决:四个因子的权重到底应该是多少?
动量给 40% 还是 25%?低波动因子在熊市里明显更有用,但在牛市中却拖后腿,它的权重是不是应该随市场状态动态变化?这些问题靠拍脑袋是解决不了的,我们需要一套系统性的方法来让数据自己“说话”。
今天这篇文章,我们就来引入一种简单但强大的工具——岭回归,让模型根据历史数据动态学习每个因子的最优权重。
一、为什么需要机器学习?
1.1 固定权重的局限
等权加权简单、稳健,不容易过拟合,但它假设所有因子在任何市场环境下都同等重要。经验加权(比如动量 40%,其余各 20%)融入了人的主观判断,但一旦设定好就不会改变。
现实是,因子的有效性会随着市场环境漂移。趋势强度因子在单边市中表现优异,但在震荡市中可能变成噪音;成交量因子在放量突破时信号强烈,但在缩量盘整时几乎无用。固定权重无法捕捉这种动态变化。
1.2 机器学习能做什么?
机器学习模型可以自动学习“输入”(各因子值)和“输出”(未来收益)之间的关系。我们不需要告诉模型哪个因子更重要,它自己会从数据中找到最优的权重组合。
而且,如果我们定期用最新的数据重新训练模型,它就能持续适应市场的变化。当某个因子开始失效时,模型会自动降低它的权重;当某个因子变得特别有效时,模型会给予它更多关注。
1.3 为什么选择岭回归?
在众多机器学习模型中,岭回归有几个非常适合我们场景的特点:
- 简单透明:模型就是一组线性权重,你可以直接看到每个因子的贡献大小。
- 防止过拟合:岭回归通过 L2 正则化惩罚过大的权重,避免模型对历史数据“死记硬背”。
- 适合小样本:我们每周只有 10 只 ETF 的数据,样本量很小,岭回归的正则化特性在这里优势明显。
- 计算快速:不需要 GPU,普通电脑几秒就能完成训练和预测。
二、岭回归的核心原理
2.1 普通线性回归的问题
普通线性回归的目标是找到一组权重 w,使得预测值 X * w 与真实值 y 之间的误差平方和最小。但在金融数据中,因子之间往往存在一定的相关性,普通线性回归对这种情况非常敏感,会导致某些权重被放大到不合理的程度。
2.2 岭回归的解决方案
岭回归在误差平方和的基础上,增加了一个惩罚项:所有权重的平方和乘以一个正则化参数 alpha。这个惩罚项会“压缩”过大的权重,迫使模型更均衡地使用各个因子的信息。
alpha 越大,权重被压缩得越厉害;alpha 越小,模型越接近普通线性回归。通常 alpha 设置在 0.1 到 10 之间,需要通过交叉验证来确定最优值。
三、用岭回归训练动态权重
3.1 数据准备:构建训练集
模型的目标是根据当期的因子值,预测下一期的收益率。因此,训练数据的 X(特征)是各 ETF 在某个日期的四个因子值,y(目标)是这些 ETF 在下一个交易日的收益率。
由于不同因子的量纲不同,我们在训练前需要对 X 做标准化处理。注意:标准化必须只使用训练集内的均值和标准差,不能使用全样本数据,否则会引入未来函数。
3.2 滚动窗口训练
为了模拟实盘环境,我们采用滚动窗口的方式进行训练和预测。具体步骤如下:
这个过程确保了在任何时间点,模型只使用“当时已知”的数据进行训练,完全避免了未来函数。
3.3 Python 实现
以下是完整的滚动岭回归训练与预测代码。
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
def rolling_ridge_signal(factor_dict, returns_df, window=252, alpha=1.0):
"""
使用滚动岭回归生成多因子轮动信号
Parameters:
– factor_dict: 字典,键为因子名称,值为因子DataFrame
– returns_df: 日收益率DataFrame
– window: 滚动训练窗口长度
– alpha: 岭回归正则化参数
Returns:
– ridge_score: 每个交易日的预测收益率DataFrame
"""
# 将所有因子合并为一个面板
factor_names = list(factor_dict.keys())
# 构建统一的MultiIndex DataFrame
all_dates = returns_df.index
all_etfs = returns_df.columns
# 初始化输出
ridge_score = pd.DataFrame(index=all_dates, columns=all_etfs, dtype=float)
# 从窗口起始日开始循环
for i in range(window, len(all_dates)):
train_start = i – window
train_end = i – 1 # 训练集结束于昨天
predict_date = all_dates[i]
# 收集训练数据
X_train_list = []
y_train_list = []
for j in range(train_start, train_end + 1):
train_date = all_dates[j]
next_date = all_dates[j + 1] if j + 1 < len(all_dates) else None
if next_date is None:
continue
# 提取该日期所有ETF的因子值
factor_values = []
for name in factor_names:
factor_df = factor_dict[name]
if train_date in factor_df.index:
factor_values.append(factor_df.loc[train_date].values)
else:
factor_values.append(np.zeros(len(all_etfs)))
X_train_list.append(np.column_stack(factor_values))
y_train_list.append(returns_df.loc[next_date].values)
if len(X_train_list) == 0:
continue
X_train = np.vstack(X_train_list)
y_train = np.concatenate(y_train_list)
# 删除含有NaN的行
valid_mask = ~np.isnan(X_train).any(axis=1) & ~np.isnan(y_train)
X_train = X_train[valid_mask]
y_train = y_train[valid_mask]
if len(X_train) < 20:
continue
# 标准化X
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# 训练岭回归模型
model = Ridge(alpha=alpha)
model.fit(X_train_scaled, y_train)
# 对当前日期进行预测
factor_values_today = []
for name in factor_names:
factor_df = factor_dict[name]
if predict_date in factor_df.index:
factor_values_today.append(factor_df.loc[predict_date].values)
else:
factor_values_today.append(np.zeros(len(all_etfs)))
X_today = np.column_stack(factor_values_today)
valid_etfs = ~np.isnan(X_today).any(axis=0)
X_today_valid = X_today[:, valid_etfs]
if X_today_valid.shape[1] == 0:
continue
X_today_scaled = scaler.transform(X_today_valid)
predictions = model.predict(X_today_scaled)
# 将预测值写入输出
valid_indices = np.where(valid_etfs)[0]
for idx, etf_idx in enumerate(valid_indices):
ridge_score.loc[predict_date, all_etfs[etf_idx]] = predictions[idx]
return ridge_score
# 使用岭回归生成综合分数
factor_dict = {
'momentum': momentum_20,
'lowvol': low_vol_factor,
'volume': volume_factor,
'trend': trend_factor
}
ridge_score = rolling_ridge_signal(factor_dict, daily_returns, window=252, alpha=1.0)
3.4 将岭回归分数嵌入轮动框架
岭回归输出的 ridge_score 是每只 ETF 的预测收益率,数值越大代表模型越看好它。我们可以直接用 idxmax 选出最优 ETF,后面的流程与之前完全一样。
# 用岭回归分数选出最优ETF
best_etf_ridge = ridge_score.idxmax(axis=1)
max_momentum = momentum_20.max(axis=1) # 风控仍用原始动量
# 后续信号处理与之前完全相同
signal_df_ridge = pd.DataFrame({
'best_etf': best_etf_ridge,
'max_momentum': max_momentum
})
signal_df_ridge['hold'] = signal_df_ridge['best_etf'].shift(1)
signal_df_ridge['max_shifted'] = signal_df_ridge['max_momentum'].shift(1)
signal_df_ridge.loc[signal_df_ridge['max_shifted'] < 0, 'hold'] = safe_asset
signal_df_ridge.dropna(inplace=True)
# 周频调仓
signal_df_ridge['weekday'] = signal_df_ridge.index.dayofweek
friday_signals_ridge = signal_df_ridge[signal_df_ridge['weekday'] == 4].copy()
friday_signals_ridge['trade_signal'] = friday_signals_ridge['hold']
friday_signals_ridge.loc[friday_signals_ridge['max_shifted'] < 0, 'trade_signal'] = safe_asset
daily_signal_ridge = friday_signals_ridge['trade_signal'].reindex(price_df.index)
daily_signal_ridge.ffill(inplace=True)
daily_signal_ridge.fillna(safe_asset, inplace=True)
四、回测对比:岭回归 vs 等权多因子
4.1 绩效指标对比
将岭回归策略加入我们第四篇的对比表中:
| 年化收益率 | 18.2% | 16.9% | 17.5% |
| 最大回撤 | -36.8% | -28.4% | -26.2% |
| 年化波动率 | 28.5% | 23.6% | 24.1% |
| 夏普比率 | 0.64 | 0.72 | 0.73 |
| 卡玛比率 | 0.49 | 0.60 | 0.67 |
| 年化换手率 | 420% | 285% | 350% |
4.2 结果解读
- 收益介于两者之间:岭回归的年化收益率高于等权多因子,但略低于单因子动量。
- 回撤控制优秀:最大回撤比等权多因子还低一些,体现了模型在风险环境中的适应能力。
- 换手率回升:这是机器学习策略的常见特征。模型权重频繁微调会导致换仓次数增加,部分抵消了收益提升。
- 夏普比率最高:综合来看,岭回归在风险调整收益上略优于等权方案。
五、机器学习策略的局限与风险
诚实地说,机器学习策略在回测中的优异表现,有相当一部分来自“运气”——模型在大量参数组合中恰好找到了适合历史数据的配置。以下是实盘前必须认清的几个风险。
5.1 过拟合风险
即使使用了岭回归的正则化,模型仍然可能捕捉到历史数据中的噪音。在金融领域,信噪比极低,任何复杂的模型都可能在回测中表现优异,实盘中却黯然失色。
5.2 参数敏感性
岭回归的 alpha 参数对结果影响很大。alpha=1.0 和 alpha=5.0 可能给出完全不同的权重分配。训练窗口长度也是一个敏感参数。这些参数的选择往往需要样本外测试来验证。
5.3 因子失效的连锁反应
如果某个因子突然失效(比如市场结构发生根本变化),机器学习模型可能需要一段时间才能“忘记”它。在此期间,失效因子仍然占据一定权重,拖累策略表现。
5.4 建议
- 用等权多因子作为主策略,机器学习版本作为辅助参考。
- 用一部分小资金单独运行机器学习策略,观察实盘与回测的差距。
- 定期对比两种方案的绩效,如果机器学习版本连续跑输等权版,果断切换。
- 不要频繁调参。一旦确定了 alpha 和窗口长度,至少运行半年再做调整。
六、本篇总结
- 岭回归提供了一种系统性的方法,让模型根据历史数据动态学习各因子的最优权重。
- 滚动窗口训练是避免未来函数的关键:只用“当时已知”的数据训练,用训练好的模型预测下一期。
- 在回测中,岭回归策略在夏普比率上优于等权多因子,但换手率有所回升。
- 机器学习策略有固有的过拟合风险,实盘应谨慎使用,建议以等权多因子为主,机器学习为辅。
下一篇预告:多因子轮动系列(六)——实盘监控与因子生命周期管理
下一篇将我们将讨论如何在实盘中持续监控各个因子的健康状况,建立因子淘汰与更替机制,以及如何将整套多因子轮动策略转化为一个可持续运行的交易系统。咱们下篇见!


![[特殊字符]DeepSeek‑Harness(DSH)小白保姆教程-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260816085112-6a817a009aabf-220x150.png)