4、因子工程基础:因子定义、因子标准化、因子去极值、因子正交化
做策略的朋友都知道,因子是策略的灵魂。但说实话,很多人把因子想得太玄乎了。我刚开始做量化那会儿,也以为因子是什么高深莫测的数学公式。后来踩了不少坑才明白——因子其实就是你对市场某个特征的量化描述。
今天咱们就把因子工程这四步走一遍:定义、标准化、去极值、正交化。每一步我都会结合实战经验来讲,保证你听完就能用。
4.1 因子定义:把想法变成数字
因子定义,说白了就是回答一个问题:你想用什么样的数据,来描述市场的什么特征?
举个例子。你觉得“这只股票最近涨得猛”,这是个想法。但怎么量化?你可以用“过去5天的涨幅”,也可以用“过去20天的涨幅”。这就是因子定义。
我个人习惯把因子分成三类:
- 价格因子:比如动量、反转、波动率
- 基本面因子:比如市盈率、市净率、ROE
- 另类因子:比如舆情情绪、资金流向
这里有个坑,我踩过好几次——因子定义要清晰、可复现。你不能说“我觉得这只股票人气高”,你得说“过去5天换手率的平均值”。
核心原则:因子定义必须满足“三可”——可量化、可计算、可回测。
来看一个简单的因子定义代码:
# 定义一个动量因子:过去5天收盘价的平均涨幅
def momentum_factor(df, window=5):
df['momentum'] = df['close'].pct_change(window)
return df
嗯,就这么简单。但别小看这一步,因子定义的好坏,直接决定了后面所有工作的质量。
4.2 因子标准化:让不同因子站在同一起跑线
你想想看,市盈率可能是几倍到几百倍,而换手率可能是0.1%到10%。这两个因子放在一起,量纲都不一样,怎么比较?
标准化就是解决这个问题的。我常用的方法有两种:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score标准化 | (x - μ) / σ | 因子近似正态分布时 |
| Min-Max标准化 | (x - min) / (max - min) | 因子有明确边界时 |
我在项目中遇到过一个问题:用Z-score标准化时,如果因子有极端值,标准化后的结果会被严重扭曲。所以后来我养成了一个习惯——先做去极值,再做标准化。顺序很重要。
我的建议:做市策略中,我一般用Z-score标准化。因为它保留了因子之间的相对关系,对后续的多因子融合更友好。
# Z-score标准化
def zscore_standardize(df, factor_col):
mean = df[factor_col].mean()
std = df[factor_col].std()
df[factor_col + '_zscore'] = (df[factor_col] - mean) / std
return df
4.3 因子去极值:别让异常值毁了你的策略
为什么会需要去极值?我给你讲个真实案例。
有一次我在做回测,发现某个因子在某个时间点突然变得特别“有效”。我兴奋了半天,结果一查——原来是那天有一只股票因为乌龙指,价格瞬间跌了90%,导致因子值变成了一个巨大的负数。这个异常值,让整个回测结果都失真了。
去极值的方法,我常用这三种:
- MAD法:基于中位数和绝对偏差,稳健性最好
- 百分位法:直接截断上下1%或5%
- 标准差法:超过3倍标准差的值被截断
我个人最推荐MAD法。为什么?因为它不受极端值本身的影响。你想想看,如果用标准差法,极端值会把标准差拉大,然后极端值反而变得“不极端”了——这是个悖论。
注意:去极值不是删除数据,而是把极端值“拉”回到一个合理的范围内。我曾经见过有人直接删除异常值,结果导致样本量不均匀,回测结果偏差很大。
# MAD法去极值
def mad_winsorize(df, factor_col, n=5):
median = df[factor_col].median()
mad = (df[factor_col] - median).abs().median()
upper = median + n * mad
lower = median - n * mad
df[factor_col] = df[factor_col].clip(lower, upper)
return df
4.4 因子正交化:消除因子间的“共线性”
这一步,很多人会忽略。但我告诉你,这一步做不好,你的多因子模型就是“一锅粥”。
什么叫共线性?简单说就是两个因子长得太像了。比如“过去5天涨幅”和“过去10天涨幅”,它们之间肯定高度相关。如果你把这两个因子都放进模型,它们会互相“抢功劳”,导致模型不稳定。
正交化的目的,就是让每个因子都“独立”出来,互不干扰。
我常用的方法是施密特正交化。它的核心思想是:先选一个基准因子,然后把其他因子中与基准因子相关的部分“剔除”掉。
核心逻辑:正交化后的因子,两两之间的相关系数为0。这样每个因子贡献的都是“增量信息”。
# 施密特正交化(简化版)
import numpy as np
def schmidt_orthogonalize(factors):
# factors: 因子矩阵,每列是一个因子
n = factors.shape[1]
ortho = np.zeros_like(factors)
ortho[:, 0] = factors[:, 0]
for i in range(1, n):
proj = np.zeros_like(factors[:, 0])
for j in range(i):
coef = np.dot(factors[:, i], ortho[:, j]) / np.dot(ortho[:, j], ortho[:, j])
proj += coef * ortho[:, j]
ortho[:, i] = factors[:, i] - proj
return ortho
嗯,代码看起来有点数学味,但实际用起来很简单。你只需要记住:正交化之后,因子之间的相关性被消除了,但每个因子的“个性”保留了下来。
4.5 本章知识体系
为了让你更直观地理解这四步的关系,我画了一张流程图:
这张图把四步流程串起来了。你从左边开始,一步步往右走。每一步都有它的意义,缺一不可。
4.6 实战中的一些体会
最后,我想分享几点实战中的体会:
- 因子不是越多越好。我见过有人搞了上百个因子,结果正交化之后发现大部分都是冗余的。我的建议是:先精挑细选10-20个有逻辑支撑的因子,再慢慢扩展。
- 去极值的参数要调。MAD法中的n值,我一般设5。但不同市场、不同因子,最优参数可能不一样。建议做一下参数敏感性分析。
- 正交化不是万能的。它只能消除线性相关性,非线性相关性它管不了。如果你发现正交化后因子之间还有明显的非线性关系,那可能需要考虑更复杂的处理方法。
一个小技巧:做完因子工程后,建议画一张因子相关性热力图。如果正交化后大部分相关系数接近0,说明你的正交化做得不错。如果还有明显的相关性,那就得回头检查一下了。
好了,因子工程的基础就讲到这里。这四步是后续多因子信号融合的基石,每一步都值得你花时间去打磨。记住:好的因子工程,能让你的策略事半功倍;差的因子工程,再好的模型也救不了。
公众号:蓝海资料掘金营,微信deep3321