4、因子计算引擎:因子定义规范、向量化计算、因子去极值、因子标准化、中性化处理

好,咱们进入正题。因子计算引擎,说白了就是整个回测系统的心脏。你前面数据清洗得再漂亮,因子算不对,一切都是白搭。我见过太多人在这块翻车了——因子定义不规范,算出来的结果自己都不敢信。

今天我把这块掰开揉碎了讲。四个核心环节:因子定义规范、向量化计算、去极值、标准化和中性化。嗯,咱们一个一个来。

4.1 因子定义规范:先把规矩立好

我个人习惯,写因子之前先定规范。没有规矩不成方圆,尤其在团队协作里,你写一个因子,别人看不懂,那就废了。

一个标准的因子定义,至少包含以下字段:

字段 说明 示例
因子名称 英文标识,建议用下划线分隔 pe_ttm
因子中文名 方便沟通 滚动市盈率
计算逻辑 伪代码或公式 总市值 / 近12个月净利润
依赖数据 需要哪些原始字段 close, net_profit
输出范围 理论取值范围 (-∞, +∞)
处理方式 是否需要去极值/标准化

举个例子,我定义一个动量因子:

# 因子定义示例
factor_name: 'momentum_20d'
chinese_name: '20日动量'
formula: 'close / shift(close, 20) - 1'
dependencies: ['close']
output_range: [-1, +∞)
need_winsorize: True
need_standardize: True
need_neutralize: False

为什么要这么细致?因为因子计算最怕歧义。我曾经在项目里遇到一个同事,他算的"市盈率"和我算的差了10倍——后来发现他用的是总股本,我用的是流通股本。这种坑,规范能帮你避开一大半。

4.2 向量化计算:别写循环,求你了

很多新手写因子,喜欢用for循环。股票3000只,时间2000天,你一个循环下去,电脑能跑冒烟。

正确的做法是向量化计算。用pandas或numpy的数组运算,一次搞定所有数据。

来看个对比:

# 错误示范:循环计算
def calc_momentum_loop(df):
    result = []
    for i in range(20, len(df)):
        ret = df['close'].iloc[i] / df['close'].iloc[i-20] - 1
        result.append(ret)
    return result

# 正确示范:向量化计算
def calc_momentum_vectorized(df):
    return df['close'] / df['close'].shift(20) - 1

看到了吗?向量化版本就一行代码。而且速度差距是数量级的——3000只股票2000天数据,循环可能要跑几分钟,向量化几秒钟搞定。

我的经验:写因子时,脑子里要时刻想着"这是数组运算"。能用shift、rolling、groupby解决的,绝对不要写for。实在遇到复杂逻辑,先用numpy的where或select,再不行就用apply,但apply也是最后的选择。

为什么会这样?因为pandas底层用的是C语言和numpy,向量化操作直接调用这些高效实现。你写个Python循环,每步都要解释器处理,自然慢。

4.3 因子去极值:别让异常值毁了你的因子

因子算出来了,但你会发现有些值特别离谱。比如某只股票因为停牌,20日收益算出来是1000%。这种极端值如果不处理,后面标准化、回归分析全都会被带偏。

常用的去极值方法有三种:

  • MAD法(中位数绝对偏差):稳健,不受极端值影响
  • 百分位法:直接砍掉头尾一定比例
  • 标准差法:超过n倍标准差的值截断

我个人最常用的是MAD法,因为它对异常值不敏感。来看代码:

def winsorize_mad(series, n=5):
    """
    MAD法去极值
    n: 阈值倍数,通常取3-5
    """
    median = series.median()
    mad = (series - median).abs().median()
    upper = median + n * mad
    lower = median - n * mad
    return series.clip(lower, upper)

我曾经遇到过一个案例:某因子在2015年股灾期间,因为几只股票连续跌停,因子值直接崩到-999%。如果不做去极值,整个回测结果都是错的。做了MAD处理后,这些异常值被限制在合理范围内,回测结果才恢复正常。

注意:去极值是在截面(同一时间点)上做的,不是在时序上。也就是说,每天对所有股票的因子值做一次去极值,而不是对某只股票的历史数据做。这个顺序搞反了,结果会出问题。

4.4 因子标准化:让不同因子站在同一起跑线

因子去完极值,接下来要标准化。为什么?因为不同因子的量纲不一样。比如市盈率可能是几十,换手率可能是百分之几,你直接拿去做回归,量纲大的因子天然占优势。

标准化的目标是把因子值映射到均值为0、标准差为1的分布上。最常用的就是Z-score标准化:

def standardize_zscore(series):
    """
    Z-score标准化
    """
    return (series - series.mean()) / series.std()

标准化之后,所有因子的均值都是0,标准差都是1。这样你在做多因子组合时,每个因子的权重才有意义。

嗯,这里有个细节要注意:标准化也是在截面上做的。每天对所有股票的因子值做标准化,而不是对某只股票的历史数据做。原因很简单——我们关心的是股票在当天的相对位置,而不是它自己的历史位置。

4.5 中性化处理:剔除你不想要的风格

中性化是因子处理里最容易被忽视的一步,但也是最重要的一步之一。

什么叫中性化?说白了,就是剔除因子中与某些风格变量相关的部分。最常见的做法是对市值和行业做中性化。

举个例子:你发现小市值股票涨得好,但你真正想研究的因子是"动量"。如果不做市值中性化,动量因子里可能混入了市值效应——小市值股票动量强,大市值股票动量弱,你分不清收益是来自动量还是来自市值。

中性化的做法很简单:用因子值对市值和行业做回归,取残差作为新的因子值。

def neutralize_factor(factor, market_cap, industry_dummies):
    """
    因子中性化处理
    factor: 待处理的因子值
    market_cap: 市值(取对数)
    industry_dummies: 行业哑变量
    """
    import statsmodels.api as sm
    
    X = pd.concat([market_cap, industry_dummies], axis=1)
    X = sm.add_constant(X)
    
    model = sm.OLS(factor, X).fit()
    residual = factor - model.predict(X)
    
    return residual

这样处理之后,因子值中与市值和行业相关的部分被剥离了,剩下的就是纯粹的因子效应。

核心要点:中性化不是必须的,取决于你的研究目的。如果你就是想研究市值因子,那就不需要做市值中性化。但如果你想研究的是"独立于市值和行业"的因子效应,那中性化就是必须的。

我自己的经验是:大部分因子都应该至少做行业中性化。因为行业之间的差异太大了,银行股的市盈率和科技股的市盈率完全没有可比性。不做行业中性化,你算出来的因子可能只是行业暴露的代理变量。

4.6 完整流程:从原始数据到干净因子

好了,四个步骤都讲完了。咱们串起来看一个完整的因子处理流程:

def factor_pipeline(raw_factor, market_cap, industry_dummies):
    """
    因子处理完整流水线
    """
    # 1. 去极值
    clean_factor = winsorize_mad(raw_factor, n=5)
    
    # 2. 标准化
    std_factor = standardize_zscore(clean_factor)
    
    # 3. 中性化(可选)
    if market_cap is not None and industry_dummies is not None:
        final_factor = neutralize_factor(std_factor, market_cap, industry_dummies)
    else:
        final_factor = std_factor
    
    return final_factor

这个流水线,每天对截面数据跑一遍。日积月累,你就得到了一个干净、可用、没有偏误的因子序列。

最后,我用一张图来总结本章的知识体系:

因子计算引擎核心流程 原始因子数据 步骤1:去极值(MAD/百分位/标准差) 剔除异常值,避免极端值干扰 步骤2:标准化(Z-score) 统一量纲,均值为0,标准差为1 步骤3:中性化(市值/行业回归取残差) 剔除风格暴露,保留纯粹因子效应 干净可用的因子数据 截面处理(每日一次)

这张图把整个流程串起来了。从原始因子数据开始,经过去极值、标准化、中性化三步处理,最终得到干净可用的因子。每一步都有明确的数学含义和业务目的。

好了,因子计算引擎的核心内容就这些。记住:规范定义是基础,向量化计算是效率,去极值和标准化是常规操作,中性化是进阶技巧。把这四步吃透了,你的因子质量就能超过90%的量化团队。


专注资料整理