4、因子计算与实现:从公式到代码的实战
好,咱们进入最核心的部分了。因子计算,说白了就是把你的投资逻辑,变成一串可以执行的数字。我见过太多人,策略想得天花乱坠,一到写代码就卡壳。今天咱们就把这块硬骨头啃下来。
4.1 因子计算公式:逻辑是骨架
每个因子背后,都有一个数学表达式。比如最常见的动量因子:
Momentum = (P_t / P_{t-n}) - 1
其中 P_t 是当前价格,P_{t-n} 是 n 天前的价格。就这么简单?嗯,但实际用起来坑不少。
再比如估值因子中的市盈率倒数(E/P):
EP = Earnings / Price
我个人习惯,在写公式前,先问自己三个问题:
- 这个因子是截面比较,还是时间序列比较?(截面看排名,时序看变化)
- 需不需要做行业中性化?(比如市盈率,银行股和科技股没法直接比)
- 极端值怎么处理?(我曾经遇到过某小市值股票一天涨了100倍,直接把因子分布拉崩了)
核心原则:因子公式是逻辑的数学化。逻辑错了,代码再漂亮也没用。
4.2 Python实现:Pandas与Numpy的配合
好,公式有了,咱们上代码。我习惯用 Pandas 做数据处理,Numpy 做数值计算。两者配合,效率很高。
先看一个简单的动量因子实现:
import pandas as pd
import numpy as np
def calc_momentum(price_df, window=20):
"""
计算动量因子
price_df: DataFrame, index=日期, columns=股票代码
"""
# 计算收益率
ret = price_df.pct_change(window)
# 我习惯用shift来对齐,避免未来信息
momentum = ret.shift(1)
return momentum
你可能会问,为什么要 shift(1)?因为我们要避免未来函数。在回测中,今天的因子值只能用昨天的数据计算。这个坑我踩过,当时回测收益高得离谱,一查发现用了未来数据,白高兴一场。
再看一个稍微复杂点的,计算市净率的倒数(BP):
def calc_bp(price_df, book_value_df):
"""
计算BP因子
"""
bp = book_value_df / price_df
# 处理无穷大和缺失值
bp = bp.replace([np.inf, -np.inf], np.nan)
# 我一般会做MAD去极值
median = bp.median()
mad = (bp - median).abs().median()
upper = median + 5 * mad
lower = median - 5 * mad
bp = bp.clip(lower, upper, axis=1)
return bp
我的小技巧:写因子函数时,尽量让输入输出都是 DataFrame。这样方便后续用 groupby 做截面处理。你想想看,如果每个因子都返回 Series,后面组合起来多麻烦。
4.3 因子暴露度计算:你到底暴露了多少?
因子暴露度,说白了就是你的组合在某个因子上的「敞口」。比如你买了很多小盘股,那你在市值因子上的暴露度就很高。
计算方式其实很简单:
# 假设 portfolio_weights 是组合权重 Series
# factor_values 是因子值 Series(已经标准化)
exposure = (portfolio_weights * factor_values).sum()
但实际项目中,我们通常要算多个因子的暴露度矩阵。我曾经做过一个多因子模型,需要同时监控价值、动量、质量、低波四个因子。代码大概是这样的:
def calc_factor_exposure(weights, factor_df):
"""
计算组合在多个因子上的暴露度
weights: Series, 股票权重
factor_df: DataFrame, columns为因子名, index为股票代码
"""
# 确保权重和因子对齐
common = weights.index.intersection(factor_df.index)
weights_aligned = weights[common]
factor_aligned = factor_df.loc[common]
# 计算暴露度
exposures = (weights_aligned.values[:, None] * factor_aligned.values).sum(axis=0)
return pd.Series(exposures, index=factor_df.columns)
注意:因子暴露度计算前,一定要做标准化。我习惯用 Z-score 或者排名百分位。否则不同量纲的因子直接相乘,结果毫无意义。
4.4 因子收益计算:这个因子到底赚不赚钱?
因子收益,就是做多因子值高的股票、做空因子值低的股票,能赚多少钱。这是检验因子有效性的核心指标。
最常用的方法是分层回测:
def calc_factor_return(factor_series, forward_return, n_groups=5):
"""
计算分层因子收益
factor_series: 当期因子值
forward_return: 下一期收益
"""
# 分组
groups = pd.qcut(factor_series, n_groups, labels=False)
# 计算每组平均收益
group_returns = forward_return.groupby(groups).mean()
# 多空组合收益(最高组 - 最低组)
long_short = group_returns.iloc[-1] - group_returns.iloc[0]
return group_returns, long_short
嗯,这里要注意。分组的时候,我遇到过因子值大量重复的情况,qcut 会报错。所以我会先加一点随机噪声:
# 处理重复值
factor_series = factor_series + np.random.normal(0, 1e-6, len(factor_series))
另外,因子收益的统计显著性也很重要。我一般会算 t 统计量:
from scipy import stats
def calc_factor_ic(factor, forward_return):
"""
计算信息系数(IC)
"""
# 去掉缺失值
mask = factor.notna() & forward_return.notna()
ic, p_value = stats.spearmanr(factor[mask], forward_return[mask])
return ic, p_value
实战经验:IC 值大于 0.05 且 p 值小于 0.05,这个因子才算初步有效。但别高兴太早,还要做样本外测试。我曾经有个因子,样本内 IC 高达 0.12,一出样本就变成 -0.03,典型的过拟合。
4.5 知识体系总览
说了这么多,咱们用一张图把整个流程串起来。我画了个流程图,帮你理清思路:
这张图把整个流程串起来了。从公式到代码,再到暴露度和收益计算,最后验证有效性。如果某个环节出问题,就回到前面迭代优化。
最后说一句:因子计算不是一锤子买卖。我做了这么多年,发现真正有效的因子,都是在反复迭代中打磨出来的。别怕改代码,怕的是不改。