因子构建与筛选:从数据到信号的实战之路
因子构建,说白了就是给市场做「体检」。
我刚开始做量化那会儿,总觉得因子越多越好。后来被市场狠狠教育过——因子多了,噪音也多了。今天我们就聊聊,怎么从一堆原始数据里,提炼出真正有用的信号。
常见动量因子:市场里的「惯性」
动量因子,本质上是利用市场的惯性效应。涨的还会涨,跌的还会跌——至少在短期内是这样。
我个人习惯把动量因子分成三类:
- 价格动量:过去N天的收益率。比如过去20天涨了5%,这就是一个信号。
- 成交量动量:过去N天的成交量变化。量在价先,这个道理你懂的。
- 复合动量:价格和成交量的组合。比如「价涨量增」就是典型的强势信号。
我在项目中遇到过一个问题:单纯用过去20天收益率做因子,结果在震荡市里频繁打脸。后来我加入了成交量确认,效果好了不少。
下面是一个简单的动量因子计算代码:
import pandas as pd
import numpy as np
def calc_momentum_factor(price_df, window=20):
"""
计算价格动量因子
price_df: DataFrame, 每列是一只股票的收盘价
window: 回看窗口
"""
# 计算过去N天的收益率
ret = price_df.pct_change(window)
# 标准化处理,让因子更稳定
factor = (ret - ret.mean()) / ret.std()
return factor
因子IC分析:检验因子的「预测力」
因子建好了,怎么知道它有没有用?IC(信息系数)就是干这个的。
IC的计算很简单:每个时间点上,算因子值和未来收益的相关系数。IC越高,说明因子预测能力越强。
我一般关注三个指标:
| 指标 | 含义 | 经验阈值 |
|---|---|---|
| IC均值 | 因子整体的预测能力 | 绝对值 > 0.02 算不错 |
| IC标准差 | 因子预测的稳定性 | 越小越好 |
| ICIR | IC均值除以标准差 | > 0.5 算优秀 |
举个例子:
def calc_ic(factor_series, forward_returns):
"""
计算截面IC
factor_series: 当前时点的因子值
forward_returns: 未来N天的收益率
"""
from scipy.stats import spearmanr
# 去掉缺失值
mask = factor_series.notna() & forward_returns.notna()
# 计算秩相关系数
ic, p_value = spearmanr(factor_series[mask], forward_returns[mask])
return ic, p_value
因子收益率分析:多空组合的「真金白银」
IC是理论值,因子收益率才是真金白银。
做法很简单:每个月按因子值把股票分成10组,做多最高组,做空最低组,看看这个多空组合能赚多少钱。
我习惯看这几个指标:
- 年化收益率:多空组合一年能赚多少
- 夏普比率:收益风险比,一般要大于1
- 最大回撤:最惨的时候亏多少
- 胜率:赚钱月份占比
我记得有一次,一个因子的年化收益率高达15%,但最大回撤超过30%。这种因子你敢用吗?反正我不敢。
def factor_return_analysis(factor_df, return_df, groups=10):
"""
因子收益率分析
factor_df: 因子值矩阵
return_df: 收益率矩阵
"""
# 按因子值分组
ranks = factor_df.rank(axis=1, pct=True)
groups_labels = (ranks * groups).astype(int).clip(0, groups-1)
# 计算每组平均收益
group_returns = {}
for g in range(groups):
mask = groups_labels == g
group_returns[g] = (return_df * mask).sum(axis=1) / mask.sum(axis=1)
# 多空组合收益
long_short = group_returns[groups-1] - group_returns[0]
return long_short
因子相关性控制:别让因子「互相打架」
因子多了,难免有相关性。比如「过去20天收益率」和「过去60天收益率」,相关性肯定高。
因子相关性高有什么问题?
- 组合后效果会打折扣
- 容易过拟合
- 解释起来很麻烦
我一般设一个阈值:相关性超过0.7的因子,只保留一个。保留哪个?看IC和ICIR,哪个好留哪个。
下面是一个相关性矩阵的示例:
def check_factor_correlation(factor_list):
"""
检查因子之间的相关性
factor_list: 因子DataFrame的列表
"""
# 合并所有因子
all_factors = pd.concat(factor_list, axis=1)
# 计算相关性矩阵
corr_matrix = all_factors.corr()
# 找出高相关对
high_corr_pairs = []
for i in range(len(corr_matrix.columns)):
for j in range(i+1, len(corr_matrix.columns)):
if abs(corr_matrix.iloc[i, j]) > 0.7:
high_corr_pairs.append(
(corr_matrix.columns[i],
corr_matrix.columns[j],
corr_matrix.iloc[i, j])
)
return corr_matrix, high_corr_pairs
因子合成方法:把「好因子」变成「好策略」
因子筛选完了,怎么合成一个综合信号?
我常用的方法有三种:
- 等权合成:每个因子权重一样。简单粗暴,但效果往往不错。
- IC加权:IC高的因子给更高权重。逻辑上合理,但要注意IC的稳定性。
- 机器学习合成:用回归或树模型学习最优权重。效果最好,但容易过拟合。
我个人习惯先用等权合成做基准,然后尝试IC加权。如果效果明显更好,再考虑机器学习。
def combine_factors(factor_dict, method='equal'):
"""
因子合成
factor_dict: {'因子名': factor_series}
method: 'equal', 'ic_weighted', 'ml'
"""
factor_df = pd.DataFrame(factor_dict)
if method == 'equal':
# 等权合成
combined = factor_df.mean(axis=1)
elif method == 'ic_weighted':
# IC加权,需要提前计算IC
# 这里假设ic_dict已经算好
weights = pd.Series(ic_dict)
weights = weights / weights.sum()
combined = factor_df.dot(weights)
return combined
本章知识体系
下面这张图,把因子构建与筛选的整个流程串起来了:
嗯,因子构建与筛选,说白了就是「去粗取精」的过程。从海量数据里找到真正有用的信号,然后组合起来,形成稳定的策略。
记住一句话:因子不是越多越好,而是越精越好。一个IC稳定、逻辑清晰的因子,胜过十个花里胡哨的因子。