4. 因子计算与实现:以动量因子为例,演示因子值的计算过程

聊到因子计算,很多人第一反应就是「拿公式一套,出结果」。嗯,理论上是这样。但我在实际项目中踩过不少坑,发现细节才是魔鬼。今天我们就拿动量因子开刀,一步步把计算过程掰开揉碎了讲。

4.1 动量因子的核心逻辑

动量因子,说白了就是「过去涨得好的,未来可能继续涨」。这个逻辑听着简单,但实现起来有讲究。

我个人习惯把动量因子分成两类:

  • 时间序列动量:看个股自身过去一段时间的收益率
  • 截面动量:看个股在所有股票中的相对排名

回测中常用的是截面动量,因为它能消除市场整体趋势的影响。你想想看,如果大盘涨了20%,个股涨了15%,这算动量强还是弱?截面动量会告诉你——弱,因为跑输了大盘。

4.2 计算步骤拆解

我们以最常见的12个月动量因子为例,也就是用过去12个月的收益率(剔除最近1个月)来构建因子。为什么要剔除最近1个月?因为短期反转效应会干扰动量信号。我在项目中遇到过,如果不做这个处理,因子表现会大打折扣。

步骤一:数据准备

你需要以下数据:

  • 个股的日度或月度收盘价(复权后)
  • 时间范围:至少13个月的历史数据
  • 股票池:全A股或特定指数成分股
注意:价格数据一定要用后复权。我曾经因为用了前复权,导致因子值出现严重偏差,查了三天才找到原因。

步骤二:计算月度收益率

先把日度数据转换成月度数据。我一般用月末收盘价来计算:

# 伪代码示例
monthly_return = (close_price_t / close_price_t-1) - 1

这里有个细节:如果某个月份停牌怎么办?我的做法是跳过该月,用上一个有效月份的数据。但要注意,连续停牌超过3个月的股票,建议直接剔除。

步骤三:计算累积收益率

动量因子需要的是过去12个月的累积收益率(剔除最近1个月):

# 计算过去12个月累积收益率(剔除最近1个月)
momentum_factor = (1 + monthly_return_t-2) * 
                  (1 + monthly_return_t-3) * 
                  ... * 
                  (1 + monthly_return_t-13) - 1

为什么是t-2到t-13?因为我们要跳过t-1(最近一个月)。这个窗口期设置,是我在多次回测中试出来的最优解。

步骤四:截面标准化

得到原始动量值后,还需要做截面标准化。常用的方法有两种:

方法 公式 适用场景
Z-score (x - mean) / std 因子值呈正态分布时
排名百分位 rank(x) / N 因子值有极端值时

我个人更推荐排名百分位法。为什么?因为动量因子经常会出现极端值——比如某只股票因为重组连续涨停,动量值会高得离谱。用Z-score会被这些极端值带偏,而排名法就稳健得多。

4.3 完整代码实现

下面是一个完整的Python实现,我用的是pandas和numpy:

import pandas as pd
import numpy as np

def calculate_momentum_factor(price_df, lookback=12, skip=1):
    """
    计算截面动量因子
    
    参数:
        price_df: DataFrame, 索引为日期, 列为股票代码, 值为复权收盘价
        lookback: int, 回溯期数(月)
        skip: int, 跳过的最近期数(月)
    
    返回:
        DataFrame, 动量因子值(已标准化)
    """
    # 计算月度收益率
    monthly_ret = price_df.pct_change()
    
    # 计算累积收益率(跳过最近skip个月)
    momentum = (1 + monthly_ret.shift(skip)).rolling(window=lookback).apply(
        lambda x: np.prod(x) - 1, raw=True
    )
    
    # 截面排名标准化
    momentum_rank = momentum.rank(axis=1, pct=True)
    
    # 处理缺失值
    momentum_rank = momentum_rank.replace([np.inf, -np.inf], np.nan)
    
    return momentum_rank

# 使用示例
# price_data = pd.read_csv('monthly_prices.csv', index_col=0, parse_dates=True)
# momentum_factor = calculate_momentum_factor(price_data)
小技巧:代码中的 np.prod 函数计算累积乘积,比循环快10倍以上。处理全A股4000多只股票时,这个优化能省下不少时间。

4.4 常见坑点与避坑指南

这部分是我最想分享的。纸上谈兵谁都会,但实战中遇到的问题才真正考验功力。

  • 幸存者偏差:回测时只用了当前存活的股票,忽略了退市的股票。结果就是因子表现被高估。我曾经因为这个原因,把一个策略的夏普比率从1.5吹到了2.0,实盘直接打脸。
  • 前视偏差:计算因子时不小心用了未来的数据。比如用t月的收盘价计算t月的因子,这就是典型的前视偏差。记住,因子值必须在t月结束后才能计算。
  • 极端值处理:动量因子天然存在尖峰厚尾特征。我建议在标准化之前先做MAD(中位数绝对偏差)去极值,效果比3-sigma好。

核心要点:因子计算不是简单的数学公式套用。数据清洗、窗口期选择、标准化方法,每一个环节都影响最终结果。我的经验是——先跑通,再优化,最后才谈得上「精确」。

4.5 知识体系总览

下面这张图总结了动量因子计算的完整流程,你可以把它当作一个检查清单:

动量因子计算流程 步骤1:数据准备 复权价格 · 月度频率 步骤2:月度收益率 pct_change() 计算 步骤3:累积收益率 滚动12月 · 跳过1月 去极值? 是 → MAD去极值 中位数绝对偏差法 否 → 直接标准化 排名百分位 / Z-score 步骤4:截面标准化 rank(pct=True) 排名百分位 最终因子值

这张图把整个流程串起来了。你从数据准备开始,一步步走到最终因子值。每个环节都有对应的处理方法和注意事项。我个人建议把这张图打印出来贴在工位上,写代码的时候对照着看,能少走很多弯路。

最后说一句:因子计算没有银弹。同样的动量因子,在不同市场、不同时间窗口下表现可能天差地别。我的做法是——先理解逻辑,再动手实现,最后用数据验证。每一步都走扎实了,结果自然不会差。


公众号:蓝海资料掘金营,微信deep3321