4. 因子构建入门
在完成数据获取与清洗之后,我们正式进入多因子研究流水线的核心环节——因子构建。所谓因子,本质上是一个把原始数据映射为可比较数值的函数:输入是行情、财务、交易或另类数据,输出是每个资产在每个截面时点上的一个数值。这个数值的质量,直接决定了后续分层回测、IC 分析和组合优化是否有意义。本节将围绕“从原始数据到因子值”的标准化流程展开,并重点剖析那些看似微小、却足以让研究结论完全反转的常见陷阱。
一、核心原理:因子是一个“可排序的截面信号”
很多初学者把因子理解成“某个指标的计算公式”,例如“20 日动量 = 收盘价 / 20 日前收盘价 - 1”。这没有错,但只对了一半。因子研究的真正对象是截面排序:在每一个调仓时点,我们需要知道“哪些资产更值得买、哪些更不值得买”。因此,因子值本身的大小并不重要,重要的是它在同一时点、同一资产池内的相对位置。
这带来两个基本要求:第一,因子必须在截面上可比,即不同资产、不同时点的数值要能放在同一把尺子上;第二,因子必须与未来收益有稳定的单调关系,而不是仅仅在样本内“看起来相关”。标准化流程的目的,就是让原始数据满足这两个要求,同时尽量保留原始信号中的有效信息。
一句话理解:因子构建不是“算出一个数”,而是“造出一把能在每个截面上公平排序的尺子”。
二、方法流程:六步标准化流水线
一个可复用的因子构建流程通常包含以下六个步骤。它们不是必须严格串行,但每一步都对应一类典型错误。
- 确定因子逻辑与方向:先明确因子值越大代表未来收益越高,还是越低。方向不统一,后续合成会互相抵消。
- 对齐时间与资产池:确保所有输入数据在同一交易日、同一可交易资产集合上对齐,避免“用未来数据算今天因子”。
- 缺失值处理:区分“真实缺失”和“无意义缺失”,决定剔除、填充还是保留为单独类别。
- 去极值:用 MAD、分位数或截断法处理异常值,防止单个极端值主导排序。
- 标准化:常用 Z-Score 或排序百分位,把不同量纲的因子拉到同一尺度。
- 中性化:视研究需要,对行业、市值等暴露进行回归取残差,剥离非目标风险。
下面是一段典型的 Python 伪代码,展示从原始数据到标准化因子值的核心逻辑。注意其中对时间对齐和去极值的处理顺序。
import numpy as np
import pandas as pd
def build_factor(raw_df, date_col, asset_col, value_col):
# 1. 按日期和资产对齐,确保每个截面独立
df = raw_df[[date_col, asset_col, value_col]].copy()
df = df.sort_values([date_col, asset_col])
# 2. 按日截面分组处理
def process_cross_section(g):
x = g[value_col].astype(float)
# 3. 缺失值:先标记,再决定是否填充
valid = x.notna()
if valid.sum() < 5:
g['factor'] = np.nan
return g
# 4. 去极值:MAD 法,中位数 ± 3 倍绝对中位差
med = x[valid].median()
mad = (x[valid] - med).abs().median()
if mad == 0:
mad = 1e-8
lower, upper = med - 3 * mad, med + 3 * mad
x_clip = x.clip(lower, upper)
# 5. 标准化:Z-Score
mu, sigma = x_clip[valid].mean(), x_clip[valid].std()
if sigma == 0:
g['factor'] = 0.0
else:
g['factor'] = (x_clip - mu) / sigma
return g
df = df.groupby(date_col, group_keys=False).apply(process_cross_section)
return df
这段代码虽然简短,却已经覆盖了流程中的关键节点。实际研究中,还需要根据因子类型调整:例如财务因子要考虑财报公布日,量价因子要考虑停牌和涨跌停,另类数据要考虑更新频率。
三、案例场景:一个动量因子的“翻车”与修复
假设我们构建一个简单的 20 日动量因子,初始版本直接用“收盘价 / 20 日前收盘价 - 1”,然后做 Z-Score。回测显示因子 IC 不稳定,分层收益单调性差。复盘后发现三个问题:
- 未处理停牌:停牌期间价格不变,导致动量被低估,复牌后跳空又造成极端值。
- 未剔除新股:上市不足 20 日的股票,20 日前价格缺失,被简单填充为 0,产生虚假信号。
- 未做行业中性:动量因子在强周期行业和防御行业间差异巨大,行业暴露掩盖了选股能力。
修复方案是:先剔除上市不足 60 日的股票,停牌日因子设为缺失,再对动量做行业和市值中性化。修复后 IC 均值从 0.02 提升到 0.05,分层收益单调性明显改善。这个案例说明,因子构建的收益往往来自对细节的严谨处理,而不是公式本身的复杂度。
四、常见陷阱与复盘清单
下面这张表汇总了因子构建中最容易踩的坑,以及对应的检查方法。建议在每次因子入库前逐项核对。
| 陷阱 | 典型表现 | 检查方法 |
|---|---|---|
| 未来函数 | 用了财报公布日之后的数据,或用了当日收盘后才知道的信息 | 核对每个字段的“可知时间”,用 point-in-time 数据 |
| 缺失值填充不当 | 用 0 或均值填充,制造虚假截面差异 | 统计缺失比例,区分随机缺失与结构性缺失 |
| 去极值过度 | 把真实极端信号也裁掉,因子变得平庸 | 对比去极值前后 IC 与换手率变化 |
| 标准化方式混用 | 有的因子用 Z-Score,有的用排名,合成时权重失真 | 统一标准化口径,或先转排名再合成 |
| 行业市值暴露未处理 | 因子收益主要来自行业 beta,而非选股 alpha | 做行业市值中性化前后对比 IC 和分层收益 |
| 样本内外不一致 | 用全样本统计量做标准化,引入未来信息 | 标准化参数必须用滚动窗口或截面内计算 |
特别提醒:标准化时使用全样本均值方差,是初学者最隐蔽的未来函数之一。正确做法是在每个截面内独立计算,或使用严格滚动的历史窗口。
五、标准化方法的选择:Z-Score 还是排名?
Z-Score 保留数值距离信息,适合分布接近正态、极端值已处理的因子;排名百分位对分布形态不敏感,抗异常值能力强,但会丢失数值差异。实务中常见做法是:先做去极值,再用 Z-Score;如果因子分布严重偏斜,改用排名后再映射到正态。两者没有绝对优劣,关键是与后续合成、回测口径保持一致。
经验法则:单因子研究阶段可以用 Z-Score 观察数值关系;多因子合成阶段建议统一转为排名或截面 Z-Score,避免量纲差异导致权重失衡。
最后需要强调,因子构建不是一次性工作,而是一个持续迭代的过程。每新增一个因子,都应该重新检查时间对齐、缺失处理、去极值和中性化是否一致。只有把标准化流程做成可复用、可审计的流水线,后续的 IC 分析、分层回测和组合优化才建立在可靠的基础之上。