第三节:因子计算与标准化

因子计算完了,原始数据拿到手了。然后呢?

直接扔进模型?别急。我见过太多人,因子算出来就直接往模型里塞,结果跑出来的结果一塌糊涂。为什么?因为原始因子数据里,有异常值、有量纲差异、有分布偏态。说白了,数据本身就不干净,模型怎么可能给你好结果?

3.1 因子计算逻辑:从原始数据到因子值

因子计算,本质上就是特征工程。把原始的交易数据、财务数据,加工成能刻画股票某个维度的指标。

举个例子,动量因子。原始数据是每天的收盘价,因子计算就是:

# 简单动量因子:过去20个交易日的累计收益率
def calc_momentum(close_prices, window=20):
    return close_prices / close_prices.shift(window) - 1

看起来简单吧?但实际项目中,坑多着呢。

我踩过的坑:

  • 复权问题:用前复权还是后复权?我习惯用后复权计算收益率,因为前复权会改变历史价格,导致因子值失真。
  • 停牌处理:停牌期间的价格是无效的。我一般会填充NaN,后续标准化时自动忽略。
  • 行业中性化:很多因子在不同行业里分布差异巨大。比如市盈率,银行股和科技股能比吗?不能。所以我会先做行业中性化,再去做标准化。
我的习惯:因子计算时,我会保留原始数据、因子值、以及计算过程中的中间变量。这样后续排查问题时,能快速定位是哪个环节出了错。

3.2 去极值:把那些「离谱」的数据拉回来

因子数据里,经常会出现一些极端值。比如某只股票因为乌龙指,一天涨了100倍。这种数据不处理,模型会被它带偏。

去极值的方法,主流的有两种:MAD法和百分位法。

3.2.1 MAD法(绝对中位差法)

MAD法的核心思想是:用中位数代替均值,用绝对中位差代替标准差。为什么?因为中位数对异常值不敏感。

def mad_winsorize(series, n=5):
    median = series.median()
    mad = (series - median).abs().median()
    upper = median + n * mad
    lower = median - n * mad
    return series.clip(lower, upper)

这里n一般取3到5。我习惯取5,因为A股市场波动大,取3容易把正常值也给截掉。

我曾经犯过的错:有一次做波动率因子,n取了3,结果把很多正常的高波动股票给截断了,导致因子区分度大幅下降。后来改成5,效果就好了很多。

3.2.2 百分位法

百分位法更直观:设定上下限的百分位,超出部分直接截断或替换。

def percentile_winsorize(series, lower=0.01, upper=0.99):
    low = series.quantile(lower)
    high = series.quantile(upper)
    return series.clip(low, high)

上下限一般取1%和99%,或者0.5%和99.5%。

注意:百分位法对样本量敏感。如果样本量太小(比如只有50只股票),1%的百分位可能只对应半只股票,这时候截断就没意义了。我一般要求样本量至少500以上才用百分位法。

3.3 标准化:让所有因子站在同一起跑线

因子之间的量纲差异太大了。市盈率可能是几十,换手率可能是百分之几,动量可能是0.1。如果不标准化,模型会天然偏向数值大的因子。

标准化的方法,主流有两种:Z-score和秩标准化。

3.3.1 Z-score标准化

Z-score的公式很简单:减去均值,除以标准差。结果就是均值为0,标准差为1的分布。

def zscore_normalize(series):
    return (series - series.mean()) / series.std()

但这里有个坑:Z-score假设数据是正态分布。如果数据是偏态的,标准化后的效果并不好。

我的经验:Z-score适合那些分布比较对称的因子,比如市值、波动率。对于偏态严重的因子(比如市盈率),我会先做对数变换,再做Z-score。

3.3.2 秩标准化(Rank Normalization)

秩标准化,说白了就是把因子值转换成排名,然后再映射到标准正态分布上。

from scipy.stats import norm

def rank_normalize(series):
    rank = series.rank()
    uniform = rank / (len(series) + 1)
    return norm.ppf(uniform)

这样做的好处是:完全消除了分布形态的影响。不管原始数据是什么分布,标准化后都是标准正态分布。

我个人更偏爱秩标准化。为什么?因为它在多因子模型里表现更稳定。Z-score对异常值敏感,而秩标准化天然免疫异常值。你想想看,一个极端值在Z-score里可能变成10个标准差,但在秩标准化里,它最多就是排名第一或最后一名。

核心结论:去极值和标准化是因子处理的两步走。先去掉极端值,再统一量纲。顺序不能乱——如果先标准化再去极值,标准化后的数据会被极端值拉偏,去极值的效果会大打折扣。

3.4 本章知识体系

下面这张图,把因子计算与标准化的核心逻辑串起来了:

因子计算与标准化核心流程 原始数据 因子计算 去极值(MAD / 百分位法) 标准化(Z-score / 秩标准化) 标准化因子 原始价格 财务数据 交易量 MAD法:n=3~5 百分位法:1%~99% Z-score:正态假设 秩标准化:分布无关 先做这一步 再做这一步 顺序不能乱

3.5 实战中的一些细节

讲完了理论,聊聊实战中我的一些习惯:

  • 因子计算频率:日频因子每天算一次,月频因子每月算一次。别把频率搞混了,否则因子值会前后不一致。
  • 数据对齐:因子计算时,一定要确保数据的时间戳对齐。我遇到过用前一天的收盘价算当天的因子,结果差了整整一天。
  • 滚动窗口:很多因子需要滚动窗口计算(比如过去20天的均值)。滚动窗口的起始点要小心,别把未来数据泄露进去。
避坑指南:我曾经在回测时发现因子表现特别好,兴奋得不行。后来一查,原来是因子计算时不小心用到了未来数据。从那以后,我每次算因子都会做一次「未来数据检查」——确保因子值在计算时点之后的信息没有被用到。

因子计算与标准化,看似基础,但细节决定成败。把这一步做扎实了,后面的模型才能跑得稳。

专注资料整理