阅读提示

这篇讲解因子质量评估的完整工具箱——profiling 模块看因子自身的统计特征(分布、覆盖率、换手率),stats 模块看因子预测能力(IC/ICIR、Fama-MacBeth)。学完之后,你能在回测之前就判断一个因子是否值得研究。

导言

在花大量时间写回测之前,先用 profiling 和 stats 把因子”体检”一遍,是专业量化研究的标准流程。Betalens 把这套流程封装成了函数,不用你写 pandas 一行行算。

常见的体检维度:

  • 覆盖率:有多少比例的股票有因子值?(太少 → 数据缺失问题)
  • 分布:因子值是正态分布还是厚尾分布?有没有异常值?(偏态严重 → 预处理不够)
  • 自相关:今天的因子值和昨天的有多大相关性?(自相关太高 → 换手率低但可能过拟合)
  • IC/ICIR:因子对下期收益有没有预测能力?(IC=0 → 因子无效)
  • 分组收益:每个分位组的真实收益差异多大?(组间差异小 → 因子选股能力弱)

profiling 模块:因子自身质量

1
2
3
4
5
6
7
8
from betalens.factor.profiling import (
distribution_summary, # 分布统计
coverage_rate, # 覆盖率
autocorrelation, # 自相关
turnover_rate, # 换手率
correlation_matrix, # 因子间相关性
cluster_analysis, # 聚类分析
)

覆盖率:有多少股票有数据

1
2
3
4
5
6
from betalens.factor.profiling import coverage_rate

cov = coverage_rate(labeled)
# labeled: single_characteristic 的返回值,MultiIndex(input_ts, code)
print(cov)
# 返回:每日的覆盖率(比例),以及全区间均值

覆盖率太低(如 <50%)说明数据缺失严重,需要检查 time_tolerance 或因子本身的披露频率。

分布统计

1
2
3
4
5
from betalens.factor.profiling import distribution_summary

dist = distribution_summary(cleaned_factor)
print(dist)
# 返回:均值、标准差、偏度、峰度、分位点(1%/25%/50%/75%/99%)

偏度(skewness)

  • 正偏(右偏):因子值集中在低值,极高值是少数异常。
  • 负偏(左偏):因子值集中在高值,极低值是少数异常。
  • 偏度绝对值 > 2 → 考虑 Rank 标准化或更强的去极值。

峰度(kurtosis)

  • 高峰度(>3):厚尾分布,极端值比正态分布更多。
  • 厚尾 → 去极值参数要更保守,或者用 Rank 标准化。

自相关分析

1
2
3
4
5
6
7
8
from betalens.factor.profiling import autocorrelation

ac = autocorrelation(cleaned_factor, max_lag=5)
print(ac)
# 返回:lag=1,2,3,4,5 的自相关系数

# lag=1 自相关 > 0.9 → 因子变化缓慢,换手率低
# lag=1 自相关 < 0.5 → 因子每天变化大,换手率高,交易成本高

换手率

1
2
3
4
5
6
7
from betalens.factor.profiling import turnover_rate

turnover = turnover_rate(labeled, method="group")
# method="group":基于分组标签的换手率(推荐)
# method="value":基于因子值的换手率
print(turnover.head())
# 返回:每日换手率序列,以及均值/标准差

换手率是评估交易成本的关键指标。年化换手率 > 300% 的因子,在考虑印花税+佣金后,通常很难跑出正超额收益。

stats 模块:因子预测能力

IC / ICIR:因子与收益的秩相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from betalens.factor.stats import IC_analysis, ICIR_analysis

# IC 时间序列分析
ic_ts = IC_analysis(
labeled, # labeled DataFrame
returns, # 下期收益率序列(需要自己算或从 Datafeed 取)
factor_key="股息率(报告期)",
)
print(ic_ts.describe())
# 返回:IC 均值、IC 标准差、t统计量、p值

# ICIR(IC 均值 / IC 标准差)
ICIR = ICIR_analysis(ic_ts)
print(f"ICIR = {ICIR:.3f}")

ICIR 的含义

ICIR 区间 解读
> 0.5 因子预测能力强,非常优质
0.3 ~ 0.5 预测能力良好,可用于组合
0.1 ~ 0.3 预测能力一般,需要配合其他因子
< 0.1 预测能力弱,单独使用价值有限
≈ 0 因子基本无效

Fama-MacBeth 回归

Fama-MacBeth 回归是截面回归的经典方法,逐月做回归,取系数均值和 t 统计量:

1
2
3
4
5
6
7
8
9
10
from betalens.factor.stats import fama_macbeth_regression

fm_result = fama_macbeth_regression(
factor_values, # 因子值
returns, # 下期收益
X_controls=["log_mktcap", "beta"], # 控制变量(可选)
)
print(fm_result["coef_mean"]) # 系数均值
print(fm_result["t_stat"]) # t 统计量
print(fm_result["p_value"]) # p 值

解读:如果股息率系数均值显著为正(t > 2),说明高股息率股票平均跑赢低股息率股票,因子的方向性预测能力得到验证。

分组收益分析

1
2
3
4
5
6
7
8
9
from betalens.factor.stats import quantile_return_summary

ret_summary = quantile_return_summary(
labeled,
returns,
factor_key="股息率(报告期)",
)
print(ret_summary)
# 返回:每个分位组(1~10)的平均收益、标准差、夏普比率

组间差异(Top 组收益 - Bottom 组收益)是多空策略的理论收益上限。如果这个差异很小(< 0.5%/月),因子在扣除交易成本后可能难以盈利。

因子相关性热力图

1
2
3
4
5
6
7
8
9
10
11
from betalens.factor.profiling import correlation_matrix

# 多个因子的因子值 DataFrame
factor_df = pd.DataFrame({
"dividend": cleaned_dividend["股息率(报告期)"],
"roe": cleaned_roe["ROE(报告期)"],
"turnover": cleaned_turnover["换手率(%)"],
}, index=cleaned_dividend.index)

corr = correlation_matrix(factor_df)
print(corr)

因子相关性 > 0.8 的两个因子,存在严重的共线性问题,在多因子模型里会相互稀释权重。优选保留 ICIR 更高的那个。

组合使用:完整因子评估流水线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from betalens.factor.profiling import coverage_rate, distribution_summary, autocorrelation, turnover_rate
from betalens.factor.stats import ICIR_analysis, quantile_return_summary

def evaluate_factor(factor_values, returns, factor_key):
"""完整因子评估报告。"""
print(f"===== {factor_key} 因子评估 =====")
print(f"覆盖率:{coverage_rate(factor_values).mean():.2%}")
print(f"分布:\n{distribution_summary(factor_values)}")
print(f"自相关(lag1):{autocorrelation(factor_values, max_lag=1)['lag_1'].mean():.3f}")
print(f"换手率:{turnover_rate(factor_values).mean():.2%}")
print(f"ICIR:{ICIR_analysis(IC_analysis(factor_values, returns, factor_key)):.3f}")
print(f"分组收益:\n{quantile_return_summary(factor_values, returns, factor_key)}")
print("=" * 40)

# 评估股息率因子
evaluate_factor(labeled, next_returns, "股息率(报告期)")

# 评估 ROE 因子
evaluate_factor(labeled_roe, next_returns, "ROE(报告期)")

开发者侧:profiling 结果的可视化

betalens.factor.stats 里内置了绘图函数:

1
2
3
4
5
6
7
8
9
from betalens.factor.stats import plot_IC_series, plot_quantile_returns

# IC 时间序列图
fig_ic = plot_IC_series(ic_ts, title="股息率 IC 时间序列")
fig_ic.show()

# 分组收益柱状图
fig_qr = plot_quantile_returns(ret_summary, title="股息率分组收益")
fig_qr.show()

这些图可以直接嵌入 Jupyter Notebook 或保存为 HTML。

常见错误

1. IC 计算用错了收益率方向

1
2
3
4
5
# 错:用当期因子值和当期收益算 IC(因果倒置)
ic_wrong = IC_analysis(factor_values, current_returns, factor_key)

# 对:用当期因子值和下一期收益算 IC(正确的预测逻辑)
ic_right = IC_analysis(factor_values, next_returns, factor_key)

2. 换手率没有考虑停牌股票

1
2
3
# 如果直接用因子值算换手率,停牌股票的因子值不变会被算成"没换手"
# 推荐用分组标签(group method)算换手率,自动排除停牌影响
turnover = turnover_rate(labeled, method="group")

3. ICIR 样本量太少导致不显著

ICIR > 0.3 但 p > 0.05,说明 IC 的 t 统计量不显著——因子的预测能力可能是样本内偶然。用更长的历史数据(至少 3 年)重新计算。

延伸阅读