4. 因子计算基础:单因子计算逻辑与标准化处理

因子计算是量化选股的核心环节。原始因子数据通常包含量价信息、财务指标或另类数据,但直接使用原始值进行分层回测会因量纲差异、极端值、行业风格干扰而产生严重偏差。本章系统讲解单因子计算逻辑、Z-score 标准化以及中性化处理,为后续 XGBoost 因子合成与分层回测奠定数据基础。

4.1 单因子计算逻辑

单因子通常分为动量因子、反转因子、波动率因子、价值因子、质量因子等类别。以下以最常见的动量因子与反转因子为例,说明计算逻辑。

因子类别 计算逻辑 典型窗口 经济含义
动量因子 过去 N 日累计收益率:MOM = (close_t / close_{t-N}) - 1 20 日、60 日、120 日 强者恒强,趋势延续
反转因子 过去 N 日累计收益率取负值:REV = -MOM 5 日、10 日、20 日 短期超买超卖后价格回归
波动率因子 过去 N 日收益率标准差 20 日、60 日 低波动异象,低波组合超额收益
价值因子 市盈率倒数(EP)、市净率倒数(BP) 最新财报 估值越低,预期收益越高
实战要点:动量因子与反转因子在 A 股市场存在明显的周期切换。短周期(5-10 日)反转效应显著,中长周期(60-120 日)动量效应占优。回测时建议同时计算多个窗口,避免单一窗口过拟合。

4.2 因子标准化:Z-score 方法

不同因子的量纲和分布差异巨大(如动量因子在 [-1, ∞) 区间,市盈率在 (0, ∞) 区间),直接合并或比较会扭曲因子权重。Z-score 标准化将因子转换为均值为 0、标准差为 1 的标准正态分布,公式如下:

# Z-score 标准化实现
import numpy as np
import pandas as pd

def zscore_standardize(factor_series):
    """
    对因子序列进行 Z-score 标准化
    参数:
        factor_series: pd.Series, 原始因子值
    返回:
        pd.Series, 标准化后的因子值
    """
    mean = factor_series.mean()
    std = factor_series.std()
    # 防止除零
    if std == 0:
        return pd.Series(np.zeros_like(factor_series), index=factor_series.index)
    return (factor_series - mean) / std

# 示例:对动量因子进行标准化
mom_factor = pd.Series([0.05, -0.02, 0.12, 0.03, -0.08, 0.07])
mom_zscore = zscore_standardize(mom_factor)
print(mom_zscore)
# 输出: 0.23, -0.85, 1.37, -0.08, -1.62, 0.95
标准化注意事项:Z-score 假设因子近似正态分布。若因子存在严重偏态或厚尾(如极端收益、小市值因子),建议先进行 MAD(中位数绝对偏差)截尾或 分位数截尾,再执行标准化,避免极端值扭曲均值和标准差。

4.3 因子中性化处理

原始因子往往与市场风格(市值、行业、波动率等)高度相关。例如,小市值股票天然具有更高的动量收益,若不剔除市值影响,动量因子的选股能力会被小市值风格干扰。中性化处理的核心是 回归残差法:

  1. 确定中性化变量:通常包括行业哑变量(申万一级/二级行业)和市值对数(log_market_cap)。
  2. 建立回归模型:以原始因子为因变量,中性化变量为自变量,进行横截面回归。
  3. 提取残差:回归残差即为剔除风格影响后的纯净因子。
# 因子中性化实现(横截面回归)
import statsmodels.api as sm

def neutralize_factor(factor_df, industry_dummies, log_mkt_cap):
    """
    对因子进行行业 + 市值中性化
    参数:
        factor_df: pd.DataFrame, 包含 'factor' 列
        industry_dummies: pd.DataFrame, 行业哑变量(one-hot)
        log_mkt_cap: pd.Series, 对数市值
    返回:
        pd.Series, 中性化后的因子残差
    """
    # 合并自变量
    X = pd.concat([industry_dummies, log_mkt_cap], axis=1)
    X = sm.add_constant(X)  # 添加截距项
    y = factor_df['factor']
    
    # 回归
    model = sm.OLS(y, X, missing='drop').fit()
    residuals = model.resid
    
    return residuals

# 示例:对动量因子进行行业市值中性化
# factor_df 包含股票代码、因子值、行业、市值等列
# neutralized_factor = neutralize_factor(factor_df, industry_dummies, log_mkt_cap)
中性化陷阱:不要对因子本身进行中性化后再做标准化,或反过来。标准流程是:原始因子 → 去极值 → 中性化 → 标准化。顺序错误会导致因子信息失真。另外,中性化变量不宜过多,避免过度拟合导致因子失去预测能力。

4.4 因子处理流程总览

因子计算与处理流程 原始量价/财务数据 单因子计算(动量/反转等) 去极值(MAD/分位数截尾) 中性化(行业+市值回归) Z-score 标准化 处理顺序不可逆 去极值 → 中性化 → 标准化 顺序错误会导致因子失真 中性化必须在标准化之前

4.5 实战案例:完整因子处理流水线

以下代码展示从原始数据到标准化因子的完整流水线,包含去极值、中性化、标准化三个核心步骤:

import pandas as pd
import numpy as np
import statsmodels.api as sm

def factor_pipeline(factor_raw, industry_dummies, log_mkt_cap, 
                    method='mad', mad_thresh=5):
    """
    因子处理完整流水线
    参数:
        factor_raw: pd.Series, 原始因子值
        industry_dummies: pd.DataFrame, 行业哑变量
        log_mkt_cap: pd.Series, 对数市值
        method: str, 去极值方法 ('mad' 或 'quantile')
        mad_thresh: float, MAD 截尾倍数
    返回:
        pd.Series, 处理后的标准因子
    """
    # 1. 去极值
    if method == 'mad':
        median = factor_raw.median()
        mad = np.median(np.abs(factor_raw - median))
        upper = median + mad_thresh * mad
        lower = median - mad_thresh * mad
        factor_clipped = factor_raw.clip(lower, upper)
    elif method == 'quantile':
        q1 = factor_raw.quantile(0.01)
        q99 = factor_raw.quantile(0.99)
        factor_clipped = factor_raw.clip(q1, q99)
    else:
        raise ValueError("method must be 'mad' or 'quantile'")
    
    # 2. 中性化(行业 + 市值)
    X = pd.concat([industry_dummies, log_mkt_cap], axis=1)
    X = sm.add_constant(X)
    y = factor_clipped
    model = sm.OLS(y, X, missing='drop').fit()
    factor_neutral = model.resid
    
    # 3. Z-score 标准化
    mean = factor_neutral.mean()
    std = factor_neutral.std()
    if std == 0:
        factor_standard = pd.Series(np.zeros_like(factor_neutral), 
                                    index=factor_neutral.index)
    else:
        factor_standard = (factor_neutral - mean) / std
    
    return factor_standard

# 使用示例
# factor_clean = factor_pipeline(mom_factor, ind_dummies, log_mkt_cap, method='mad')
流水线验证:处理后的因子应满足:① 无极端值(MAD 截尾后);② 与行业和市值相关系数接近 0(中性化效果);③ 均值为 0,标准差为 1(标准化效果)。建议在回测前绘制因子分布直方图与 Q-Q 图,确认处理质量。

4.6 常见问题与避坑指南

问题 原因 解决方案
标准化后因子仍有偏态 原始因子分布严重偏斜,Z-score 假设不成立 先做 Box-Cox 变换或 rank 标准化(百分位排序)
中性化后因子预测能力下降 中性化变量过多,剔除了有效信息 仅保留行业和市值,避免加入过多风格因子
不同截面期因子分布差异大 市场环境变化导致因子均值/标准差漂移 每个截面期独立进行标准化,不使用全局参数
去极值后因子信息损失 截尾阈值过小,误删有效极端信号 MAD 阈值设为 5-7 倍,分位数截尾设为 0.5%-99.5%