第1章:多因子交叉特征——从加减乘除到条件因子
大家好,我是老张。今天咱们聊聊因子挖掘里最基础也最实用的技巧——多因子交叉特征。
说实话,我刚开始做量化那会儿,也迷信过复杂的机器学习模型。后来发现,真正赚钱的策略,往往就靠几个精心构造的交叉特征。嗯,这章我就把压箱底的经验掏出来。
1.1 因子间的加减乘除组合
先问个问题:为什么要把因子组合起来?
单个因子就像一把螺丝刀,能拧螺丝但修不了车。组合因子就像工具箱,能应对复杂市场。我个人习惯,拿到新数据先做三件事:加、减、乘、除。
加法与减法
加法适合捕捉「共振」信号。比如动量因子加上成交量因子,能过滤掉无量上涨的假突破。
减法更常用。我记得有个项目,用「短期动量减去长期动量」构造了一个反转因子,效果比单独用任何一个都好。
# 加减法示例
import pandas as pd
import numpy as np
# 假设已有因子数据
df['momentum_short'] = df['close'].pct_change(5)
df['momentum_long'] = df['close'].pct_change(20)
# 差值因子:短期相对长期的超额动量
df['momentum_diff'] = df['momentum_short'] - df['momentum_long']
# 和值因子:综合动量强度
df['momentum_sum'] = df['momentum_short'] + df['momentum_long']
乘法与除法
乘法能放大信号。比如「换手率 × 波动率」可以识别出异常活跃的股票。
除法更讲究。我做过一个避坑指南:分母不能为0。曾经有个实习生直接用原始数据做除法,结果一堆inf,回测曲线直接崩了。
# 乘除法示例
epsilon = 1e-8
# 乘法:波动率调整后的动量
df['volatility'] = df['close'].pct_change().rolling(20).std()
df['momentum_vol_adj'] = df['momentum_short'] * df['volatility']
# 除法:换手率标准化(加epsilon防除零)
df['turnover'] = df['volume'] / df['shares_outstanding']
df['turnover_norm'] = df['turnover'] / (df['turnover'].rolling(60).mean() + epsilon)
1.2 条件因子:高动量且低波动
条件因子说白了就是「如果...那么...」的逻辑。我最常用的场景是:高动量且低波动。
为什么?高动量说明趋势强,低波动说明风险可控。两者结合,胜率能提高不少。
# 条件因子示例
# 定义条件:动量排名前30%,波动排名后30%
momentum_rank = df['momentum_short'].rank(pct=True)
volatility_rank = df['volatility'].rank(pct=True)
# 条件因子:高动量(前30%)且低波动(后30%)
df['high_mom_low_vol'] = np.where(
(momentum_rank > 0.7) & (volatility_rank < 0.3),
1, # 满足条件
0 # 不满足
)
1.3 因子正交化处理
因子之间往往有相关性。比如动量因子和换手率因子,经常高度相关。如果不处理,模型会学偏。
正交化的核心思想:把相关因子变成独立因子。我最常用的是施密特正交化。
# 施密特正交化示例
from sklearn.linear_model import LinearRegression
def orthogonalize(factor_a, factor_b):
"""
将factor_b对factor_a做正交化
返回:与factor_a正交的factor_b_residual
"""
model = LinearRegression()
model.fit(factor_a.values.reshape(-1, 1), factor_b)
predicted = model.predict(factor_a.values.reshape(-1, 1))
residual = factor_b - predicted
return residual
# 使用:将换手率对动量做正交化
df['turnover_orth'] = orthogonalize(df['momentum_short'], df['turnover'])
你想想看,正交化后的因子,每个都携带独立信息。做多因子模型时,不会出现「两个因子其实是一回事」的尴尬。
1.4 因子标准化与中性化
标准化和中性化,是因子处理的两大基石。我见过太多人跳过这步,结果模型一塌糊涂。
标准化:让因子可比
不同因子的量纲不同。比如动量可能是0.05,波动率可能是0.3。直接放一起,模型会偏向数值大的因子。
# Z-score标准化
def zscore_standardize(series):
return (series - series.mean()) / series.std()
# 或者用MAD(中位数绝对偏差),更稳健
def mad_standardize(series):
median = series.median()
mad = np.median(np.abs(series - median))
return (series - median) / (mad + epsilon)
df['momentum_z'] = zscore_standardize(df['momentum_short'])
df['volatility_z'] = mad_standardize(df['volatility'])
中性化:剔除干扰因素
中性化是剔除因子中「不想要」的成分。最常见的是市值中性化和行业中性化。
举个例子:小市值股票天然波动大。如果你不做市值中性化,波动率因子其实包含了市值信息。
# 市值中性化示例
def market_cap_neutralize(factor, market_cap):
"""
对因子做市值中性化
返回:剔除市值影响后的残差
"""
model = LinearRegression()
model.fit(np.log(market_cap).values.reshape(-1, 1), factor)
predicted = model.predict(np.log(market_cap).values.reshape(-1, 1))
residual = factor - predicted
return residual
# 使用
df['volatility_neutral'] = market_cap_neutralize(
df['volatility'],
df['market_cap']
)
我曾经犯过一个错:先标准化再中性化,结果顺序搞反了。标准化应该在最后一步做,因为中性化会改变因子的分布。
知识体系总览
下面这张图,是我自己总结的交叉特征处理流程。你照着这个顺序做,基本不会出错。
这张图展示了完整的处理链条:原始因子 → 预处理(标准化/中性化/正交化)→ 交叉特征构造 → 最终因子库。每一步都不可或缺。
实战中的避坑总结
最后,我把自己踩过的坑整理成一张表,你写代码时对照着检查:
| 操作 | 常见错误 | 正确做法 |
|---|---|---|
| 除法 | 分母为0导致inf | 加epsilon或np.where处理 |
| 条件因子 | 固定阈值不适应市场 | 用滚动分位数动态设定 |
| 正交化 | 忘记检查正交性 | 计算相关系数矩阵验证 |
| 标准化 | 在时序上做标准化 | 必须在截面上做 |
| 中性化 | 先标准化再中性化 | 先中性化,最后标准化 |
嗯,这章的内容就这些。记住一句话:好的交叉特征,胜过十个平庸的单因子。下一章咱们聊时间序列特征,到时候见。
公众号:蓝海数据掘金营,微信 deep3321