摘要: 本文是“多因子轮动”系列的第五篇。前面四篇我们完成了从因子构建到回测对比的全流程,并使用等权和经验权重合成了综合分数。但这些固定权重无法适应市场环境的变化。今天,我们将引入机器学习中的岭回归模型,让它根据历史数据动态学习各因子的最优权重。我们将详细讲解如何使用滚动窗口训练模型、如何避免未来函数、以及如何将模型输出的权重嵌入到现有的多因子轮动框架中。最后,我们会诚实讨论机器学习在实盘中的局限与风险。读完本文,你将拥有一个能够自适应市场的多因子轮动策略。


大家好,我是你们的老朋友。

前面四篇文章,我们完整地走完了多因子轮动的全流程。在第四篇的回测对比中,我们采用等权或经验加权来合成综合分数,取得了不错的成绩。但有一个问题始终悬而未决:四个因子的权重到底应该是多少?

动量给 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 滚动窗口训练

为了模拟实盘环境,我们采用滚动窗口的方式进行训练和预测。具体步骤如下:

  1. 设定一个训练窗口长度,比如 252 个交易日(约一年)。
  2. 在窗口内,用所有 ETF 的因子值作为 X,下一日收益率作为 y,训练一个岭回归模型。
  3. 用训练好的模型对当前日期的 ETF 因子值进行预测,得到每只 ETF 的预测收益率。
  4. 将预测收益率作为综合分数,选出最高的那只 ETF。
  5. 每个交易日重复以上步骤,窗口随时间向前滚动。

这个过程确保了在任何时间点,模型只使用“当时已知”的数据进行训练,完全避免了未来函数。

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.640.720.73
卡玛比率0.490.600.67
年化换手率420%285%350%

4.2 结果解读

  • 收益介于两者之间:岭回归的年化收益率高于等权多因子,但略低于单因子动量。
  • 回撤控制优秀:最大回撤比等权多因子还低一些,体现了模型在风险环境中的适应能力。
  • 换手率回升:这是机器学习策略的常见特征。模型权重频繁微调会导致换仓次数增加,部分抵消了收益提升。
  • 夏普比率最高:综合来看,岭回归在风险调整收益上略优于等权方案。

五、机器学习策略的局限与风险

诚实地说,机器学习策略在回测中的优异表现,有相当一部分来自“运气”——模型在大量参数组合中恰好找到了适合历史数据的配置。以下是实盘前必须认清的几个风险。

5.1 过拟合风险

即使使用了岭回归的正则化,模型仍然可能捕捉到历史数据中的噪音。在金融领域,信噪比极低,任何复杂的模型都可能在回测中表现优异,实盘中却黯然失色。

5.2 参数敏感性

岭回归的 alpha 参数对结果影响很大。alpha=1.0alpha=5.0 可能给出完全不同的权重分配。训练窗口长度也是一个敏感参数。这些参数的选择往往需要样本外测试来验证。

5.3 因子失效的连锁反应

如果某个因子突然失效(比如市场结构发生根本变化),机器学习模型可能需要一段时间才能“忘记”它。在此期间,失效因子仍然占据一定权重,拖累策略表现。

5.4 建议

  • 用等权多因子作为主策略,机器学习版本作为辅助参考。
  • 用一部分小资金单独运行机器学习策略,观察实盘与回测的差距。
  • 定期对比两种方案的绩效,如果机器学习版本连续跑输等权版,果断切换。
  • 不要频繁调参。一旦确定了 alpha 和窗口长度,至少运行半年再做调整。

六、本篇总结

  • 岭回归提供了一种系统性的方法,让模型根据历史数据动态学习各因子的最优权重。
  • 滚动窗口训练是避免未来函数的关键:只用“当时已知”的数据训练,用训练好的模型预测下一期。
  • 在回测中,岭回归策略在夏普比率上优于等权多因子,但换手率有所回升。
  • 机器学习策略有固有的过拟合风险,实盘应谨慎使用,建议以等权多因子为主,机器学习为辅。

下一篇预告:多因子轮动系列(六)——实盘监控与因子生命周期管理

下一篇将我们将讨论如何在实盘中持续监控各个因子的健康状况,建立因子淘汰与更替机制,以及如何将整套多因子轮动策略转化为一个可持续运行的交易系统。咱们下篇见!

更多推荐