第24章 多因子基差模型:因子筛选与降维(PCA)、因子权重优化(风险平价)、组合构建与回测
做基差交易的朋友,经常会遇到一个头疼的问题:因子太多了。
你想想看,光我能想到的就有几十个——持仓量、成交量、波动率、期限结构斜率、库存、基差动量、资金流向……每个看起来都有点道理,但真要一起用,模型就炸了。多重共线性、过拟合、因子拥挤,这些问题我早年都踩过坑。
这一章,我们就来解决这个问题。我会带你走一遍完整的流程:因子筛选与降维 → 权重优化 → 组合构建 → 回测验证。说白了,就是教你怎么从一堆因子中,挑出最核心的几个,然后科学地分配权重,最后做出一个能打的交易组合。
24.1 因子筛选与PCA降维
先说说因子筛选。我个人习惯,第一步不是跑模型,而是先做相关性分析。把候选因子两两之间的相关系数算出来,如果超过0.8,我一般会只保留一个。为什么?因为高度相关的因子放进模型,不仅不会提升预测能力,反而会让权重估计变得极不稳定。
举个例子,我曾在项目中同时用了「近月合约持仓量变化」和「远月合约持仓量变化」,结果两个因子相关系数高达0.92。回测时表现不错,但一上实盘就翻车。后来发现,这两个因子本质上反映的是同一个信息——资金在移仓。去掉一个后,模型反而更稳健了。
筛选完相关性高的因子后,下一步就是降维。这里我推荐主成分分析(PCA)。它的核心思想很简单:把一堆相关的因子,压缩成少数几个互不相关的「主成分」,每个主成分都是原始因子的线性组合。
具体怎么做?看代码:
import pandas as pd
import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# 假设 factor_df 是原始因子数据,行是时间,列是因子
# 先标准化
scaler = StandardScaler()
factor_scaled = scaler.fit_transform(factor_df)
# 执行PCA
pca = PCA(n_components=0.85) # 保留85%方差
pca_result = pca.fit_transform(factor_scaled)
# 查看每个主成分的方差解释比例
print("解释方差比例:", pca.explained_variance_ratio_)
print("累计解释方差:", np.cumsum(pca.explained_variance_ratio_))
# 查看因子载荷(每个原始因子对主成分的贡献)
loadings = pd.DataFrame(
pca.components_.T,
index=factor_df.columns,
columns=[f'PC{i+1}' for i in range(pca.n_components_)]
)
print(loadings)
做完PCA后,你可能会问:这些主成分怎么解释?说实话,PCA的缺点就是可解释性差。PC1可能是「综合动量因子」,PC2可能是「波动率因子」,但很难精确命名。不过对于量化交易来说,只要主成分能稳定预测基差走势,解释不了也没关系。
24.2 因子权重优化:风险平价
降维之后,我们得到了几个主成分因子。接下来要解决的问题是:每个因子该分配多少权重?
传统做法是用等权或市值加权,但我不太推荐。等权看似公平,实际上风险贡献并不均等——波动大的因子会主导整个组合的风险。我更喜欢用风险平价(Risk Parity)。
风险平价的核心思想:让每个因子对组合总风险的贡献相等。这样就不会出现某个因子「一家独大」的情况,组合更稳健。
数学上,我们要解一个优化问题:
from scipy.optimize import minimize
import numpy as np
def risk_parity_weights(cov_matrix):
"""
计算风险平价权重
cov_matrix: 因子收益率的协方差矩阵
"""
n = cov_matrix.shape[0]
def risk_contribution(w):
# 计算每个因子的风险贡献
portfolio_var = w @ cov_matrix @ w
marginal_contrib = cov_matrix @ w
risk_contrib = w * marginal_contrib / np.sqrt(portfolio_var)
return risk_contrib
def objective(w):
# 目标:让所有因子的风险贡献相等
rc = risk_contribution(w)
target = np.mean(rc)
return np.sum((rc - target)**2)
# 约束:权重和为1,且非负
constraints = ({'type': 'eq', 'fun': lambda w: np.sum(w) - 1})
bounds = [(0, 1) for _ in range(n)]
# 初始权重:等权
w0 = np.ones(n) / n
result = minimize(objective, w0, method='SLSQP',
bounds=bounds, constraints=constraints)
return result.x
# 假设 factor_returns 是主成分因子的日收益率矩阵
cov = np.cov(factor_returns.T)
weights = risk_parity_weights(cov)
print("风险平价权重:", weights)
我曾经在2021年用风险平价做了一组商品基差因子的组合,效果比等权好很多。最大回撤从15%降到了8%,夏普比率从1.2提升到了1.8。说白了,风险平价就是让组合「睡得安稳」——不会因为某个因子突然暴雷而崩盘。
24.3 组合构建与回测
因子权重确定后,组合构建就水到渠成了。我们按权重把主成分因子合成一个综合因子得分,然后根据得分排序,做多得分最高的品种,做空得分最低的品种。
回测时,我一般会关注几个关键指标:年化收益率、夏普比率、最大回撤、胜率、盈亏比。但还有一个容易被忽略的指标——因子拥挤度。如果某个因子被太多资金跟踪,它的超额收益会快速衰减。
下面是一个完整的回测框架:
import backtrader as bt
import pandas as pd
class MultiFactorStrategy(bt.Strategy):
params = (
('lookback', 20), # 因子计算窗口
('top_n', 5), # 做多品种数
('bottom_n', 5), # 做空品种数
)
def __init__(self):
# 这里假设 self.factors 是预处理好的因子数据
self.factor_scores = {}
def next(self):
# 获取当前时间点的因子得分
current_scores = {}
for data in self.datas:
symbol = data._name
score = self.calc_composite_score(data)
current_scores[symbol] = score
# 排序并选择多空组合
sorted_scores = sorted(current_scores.items(),
key=lambda x: x[1], reverse=True)
long_symbols = [s[0] for s in sorted_scores[:self.p.top_n]]
short_symbols = [s[0] for s in sorted_scores[-self.p.bottom_n:]]
# 执行交易
for data in self.datas:
symbol = data._name
if symbol in long_symbols:
self.order_target_percent(data, target=1.0/self.p.top_n)
elif symbol in short_symbols:
self.order_target_percent(data, target=-1.0/self.p.bottom_n)
else:
self.order_target_percent(data, target=0)
def calc_composite_score(self, data):
"""计算综合因子得分"""
# 这里用PCA主成分和风险平价权重合成
# 实际代码需要根据你的因子数据实现
pass
24.4 知识体系总览
下面这张图,是我自己总结的多因子基差模型的核心流程。你可以把它当作一个检查清单,做每一步时对照一下:
这张图里,我特别想强调「动态反馈」这个环节。很多人的模型做出来就固定了,不再更新。但市场是活的,因子有效性会随时间变化。我建议至少每季度重新做一次PCA和风险平价,看看因子结构有没有变化。
24.5 实战中的几个坑
最后,分享几个我踩过的坑,希望能帮你少走弯路:
- 因子数量不是越多越好。 我曾经用20个因子做PCA,结果前三个主成分只解释了40%的方差,说明因子之间相关性太弱,降维效果很差。后来我砍到8个核心因子,解释方差反而提升到了70%。
- 风险平价不是万能药。 在趋势性行情中,风险平价会拖累收益,因为它强制分散风险。如果你对某个因子特别有信心,可以适当偏离风险平价,但不要偏离太多。
- 回测时注意交易成本。 多因子模型通常换手率较高,如果不考虑滑点和手续费,回测结果会严重失真。我一般按双边万三计算成本,如果策略收益低于这个水平,说明不值得实盘。
公众号:蓝海资料掘金营,微信deep3321