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 知识体系总览

说了这么多,咱们用一张图把整个流程串起来。我画了个流程图,帮你理清思路:

因子计算与实现核心流程 因子公式定义 动量、价值、质量等 Python代码实现 Pandas + Numpy 因子暴露度计算 组合敞口分析 因子收益计算 分层回测、IC分析 因子有效性验证 统计显著性、样本外测试、稳健性检验 迭代优化 核心原则:逻辑先行 → 代码实现 → 暴露度监控 → 收益验证 → 迭代优化 避坑:未来函数、过拟合 数据对齐、标准化

这张图把整个流程串起来了。从公式到代码,再到暴露度和收益计算,最后验证有效性。如果某个环节出问题,就回到前面迭代优化。

最后说一句:因子计算不是一锤子买卖。我做了这么多年,发现真正有效的因子,都是在反复迭代中打磨出来的。别怕改代码,怕的是不改。

蓝海数据掘金营,专注资料整理