3. 单因子分析基础:因子定义、因子值计算、分组回测框架搭建
好,咱们进入正题。单因子分析,说白了就是回答一个问题:这个因子到底能不能赚钱?
我刚开始做量化那会儿,总觉得因子越多越好。后来被市场狠狠教育了几次,才明白——一个靠谱的单因子分析框架,比一百个花哨的因子都重要。今天我就把这块的实操经验掰开揉碎讲给你听。
3.1 因子定义:别小看这一步
因子定义,就是给因子一个明确的数学表达式。比如经典的动量因子:
Momentum = (close_price_t - close_price_{t-20}) / close_price_{t-20}
嗯,看起来很简单对吧?但这里有个坑——定义不清晰,后面全白干。
我个人习惯,定义因子时一定要明确三点:
- 计算窗口:用过去多少天的数据?20天?60天?
- 价格选择:用收盘价?还是用VWAP(成交量加权均价)?
- 处理方式:要不要做对数变换?要不要去极值?
⚠️ 我曾经踩过的坑: 定义动量因子时用了复权价格,结果回测表现特别好。后来才发现,复权价格包含了分红送股的信息,这相当于把未来的信息泄露到了因子计算中。从此以后,我定义因子一律用不复权价格。
这里我给出一个标准的因子定义模板,你直接套用就行:
| 字段 | 说明 | 示例 |
|---|---|---|
| 因子名称 | 英文标识,建议用大写 | MOM_20D |
| 因子描述 | 一句话说清楚因子含义 | 过去20个交易日的累计收益率 |
| 计算公式 | 明确的数学表达式 | (P_t - P_{t-20}) / P_{t-20} |
| 数据来源 | 用哪个数据库的哪个字段 | 日行情表.close_price |
| 处理规则 | 去极值、中性化等 | MAD去极值,市值行业中性化 |
3.2 因子值计算:代码怎么写才靠谱
定义好了,接下来就是算。你想想看,A股有5000多只股票,每天都要算一遍因子值,这代码效率必须得跟上。
我个人推荐用 pandas + numpy 做向量化计算,别用循环。来看个例子:
import pandas as pd
import numpy as np
def calc_momentum_factor(df, window=20):
"""
计算动量因子
df: 包含 'close' 列的DataFrame,索引为日期
"""
# 向量化计算,一行搞定
df['MOM'] = df['close'].pct_change(periods=window)
return df
# 使用示例
price_data = pd.DataFrame({
'close': [10, 10.5, 11, 10.8, 11.2, ...]
}, index=pd.date_range('2024-01-01', periods=100))
result = calc_momentum_factor(price_data)
print(result.head())
💡 小技巧: 计算因子值时,我习惯把结果存成 宽表格式(日期为行,股票代码为列)。这样后续做截面分析、分组回测时,数据操作会方便很多。
这里要注意一个细节:因子值的频率要和回测频率一致。如果你做的是月度调仓,那就按月计算因子值;如果是周度调仓,就按周算。别搞混了。
3.3 分组回测框架:搭建你的因子检验流水线
因子值算出来了,怎么知道它好不好?分组回测是最直观的方法。
核心逻辑很简单:
- 每个月(或每周)按因子值大小把股票分成N组
- 每组等权重或市值加权买入
- 持有到下个调仓日,计算收益
- 重复以上步骤,得到每组的历史收益曲线
如果因子有效,那么分组收益应该是单调的——第1组(因子值最大)收益最高,第N组(因子值最小)收益最低。
下面是我自己用的一个分组回测框架,你直接拿去改改就能用:
import pandas as pd
import numpy as np
def group_backtest(factor_df, return_df, n_groups=5, rebalance_freq='M'):
"""
分组回测框架
factor_df: 因子值DataFrame (日期×股票)
return_df: 收益率DataFrame (日期×股票)
n_groups: 分组数
rebalance_freq: 调仓频率 ('M'=月度, 'W'=周度)
"""
# 获取调仓日期
rebalance_dates = pd.date_range(
start=factor_df.index[0],
end=factor_df.index[-1],
freq=rebalance_freq
)
group_returns = {i: [] for i in range(1, n_groups+1)}
for date in rebalance_dates:
if date not in factor_df.index:
continue
# 获取当期因子值
current_factor = factor_df.loc[date].dropna()
# 分组
current_factor = current_factor.rank(pct=True)
groups = pd.cut(current_factor, bins=n_groups,
labels=range(1, n_groups+1))
# 计算下一期收益
next_date = factor_df.index[factor_df.index > date][0]
next_returns = return_df.loc[next_date]
# 记录每组收益
for group in range(1, n_groups+1):
stocks_in_group = groups[groups == group].index
group_return = next_returns[stocks_in_group].mean()
group_returns[group].append(group_return)
# 计算累计收益
result = pd.DataFrame(group_returns)
result = (1 + result).cumprod()
return result
📊 分组回测的核心指标:
- 单调性:分组收益是否严格单调递增/递减
- 多空收益:第1组收益 - 第N组收益,越大越好
- IC值:因子值与下期收益的截面相关系数
- IR值:IC的均值除以标准差,衡量因子稳定性
说到IC值,我再多提一嘴。IC值分两种:
- Pearson IC:线性相关系数,对异常值敏感
- Spearman Rank IC:秩相关系数,更稳健
我个人习惯用 Rank IC,因为因子值经常有极端值,用秩相关能避免被几个异常点带偏。
3.4 知识体系总览
说了这么多,我把整个单因子分析的流程画成了一张图,方便你理解:
💡 我的经验: 刚开始做因子分析时,别追求复杂的模型。先把分组回测框架搭稳了,把IC计算、单调性检验这些基本功练扎实。我见过太多人一上来就搞机器学习因子,结果连基础的单因子检验都没通过,白费功夫。
好了,单因子分析的基础框架就讲到这里。记住一句话:因子定义要清晰,计算要高效,回测要严谨。把这三点做到位,你的因子研究就成功了一半。