4. 双变量特征构造:加减乘除、比率、差值等组合特征

各位好,欢迎来到第四章。

上一章我们聊了单变量特征构造,说白了就是怎么把一个字段玩出花来。但做因子挖掘的都知道,真正的好因子,往往藏在两个变量之间的关系里。我入行那会儿,有个老前辈跟我说过一句话,我一直记着:「单变量看分布,双变量看关系」。今天我们就来聊聊这个「关系」怎么构造。

4.1 为什么需要双变量特征?

先问大家一个问题:你手里有两个因子,各自的信息比率都不高,但把它们组合起来,效果却出奇的好。为什么会这样?

我个人习惯把因子分为两类:一类是「独立信号」,另一类是「关系信号」。独立信号就是单个变量本身携带的信息,比如市盈率、换手率。而关系信号,是两个变量之间的差值、比率、乘积等,它捕捉的是「相对状态」。举个例子,短期均线和长期均线的差值,比单独看任何一条均线都更能反映趋势的强弱。

我在项目中遇到过这样一个案例:一个CTA策略,单独看价格动量因子,夏普只有0.6;单独看波动率因子,夏普0.8。但把两者做比值——动量除以波动率,夏普直接跳到1.4。说白了,这个比值衡量的是「单位风险下的动量」,比单独看任何一个都更稳定。

4.2 双变量特征的基本操作

双变量特征构造,本质上就是两个变量之间的数学运算。我把它归纳为四大类:

操作类型 公式 典型应用场景
加法 A + B 综合两个维度的信号,如动量+价值
减法 A - B 捕捉差值、偏离度,如均线差、价差
乘法 A × B 捕捉交互效应,如量价配合
除法 A / B 标准化、比率化,如市盈率、夏普比

嗯,这里要注意:不是所有组合都有意义。我见过有人把两个完全不相关的因子做除法,结果噪声比信号还大。所以,做双变量构造之前,先问问自己:这两个变量之间,有没有经济含义上的关联?

4.3 差值特征:捕捉偏离度

差值特征,说白了就是「A比B多多少」。这是我最喜欢用的一类特征,因为它天然具有均值回归的特性。

举个例子,短期均线减去长期均线。当差值为正且很大时,说明短期涨得太快,有回调风险;当差值为负且很大时,说明短期跌得太狠,有反弹机会。这就是经典的MACD指标的核心思想。

我在做股票因子时,经常用「当日成交量减去过去20日均量」这个差值特征。当差值为正且很大时,说明放量了,可能有资金介入;当差值为负且很大时,说明缩量了,市场情绪冷淡。这个因子在A股市场效果不错。

核心要点:差值特征适合捕捉「偏离」和「回归」。但要注意,差值的尺度受原始变量量纲影响,不同股票之间可能不可比。建议做标准化处理。

4.4 比率特征:消除量纲影响

比率特征,说白了就是「A是B的多少倍」。它的最大好处是消除了量纲影响,让不同股票、不同时间段的特征具有可比性。

比如市盈率,就是价格除以每股收益。这个比率让不同价格的股票可以放在一起比较。再比如夏普比率,是超额收益除以波动率,衡量的是「单位风险下的收益」。

我个人习惯在构造比率特征时,注意分母不能为零。我曾经因为分母出现零值,导致整个因子矩阵出现无穷大,回测结果直接崩了。嗯,从那以后,我每次做除法都会加一个极小值epsilon。

# 比率特征构造示例
import numpy as np
import pandas as pd

def safe_ratio(a, b, epsilon=1e-8):
    """安全的比率计算,避免除零"""
    return a / (b + epsilon)

# 示例:计算动量/波动率
momentum = pd.Series([0.05, -0.02, 0.03, 0.01])
volatility = pd.Series([0.02, 0.03, 0.01, 0.00])  # 注意最后一个为0
ratio = safe_ratio(momentum, volatility)
print(ratio)
# 输出:2.5, -0.6667, 3.0, 100000.0  —— 最后一个被epsilon兜底了
避坑指南:我曾经在回测中直接用A/B,结果分母出现0,导致因子值变成inf。回测结果看起来很好,实盘却一塌糊涂。后来才发现是除零问题。所以,做比率特征时,一定要处理分母为0的情况。

4.5 乘法和加法:捕捉交互效应

乘法和加法,更多是用于捕捉两个变量之间的「协同效应」。比如量价配合:价格上涨且成交量放大,说明上涨有支撑;价格上涨但成交量萎缩,说明上涨乏力。这个「量价配合」就可以用价格变化乘以成交量变化来构造。

加法相对简单,就是把两个信号做加权平均。但要注意,加法之前最好把两个信号都标准化,否则量纲大的那个会主导结果。

我建议的做法是:先对每个因子做z-score标准化,然后再做加法或乘法。这样能保证每个因子贡献的权重是可控的。

# 标准化后的加法/乘法
from scipy.stats import zscore

factor_a = pd.Series([100, 200, 150, 180])  # 量纲大
factor_b = pd.Series([0.01, 0.02, 0.015, 0.018])  # 量纲小

# 直接加法:factor_a主导
direct_add = factor_a + factor_b
print(direct_add)
# 输出:100.01, 200.02, 150.015, 180.018 —— 几乎看不出factor_b的影响

# 标准化后加法:两者贡献均衡
z_a = zscore(factor_a)
z_b = zscore(factor_b)
standardized_add = z_a + z_b
print(standardized_add)
# 输出:两个因子各占一半权重

4.6 实战案例:构造一个双变量因子

好了,理论说了不少,我们来个实战。假设我们要构造一个「相对强弱因子」,用两个变量:个股收益率和市场收益率。

思路很简单:个股收益率减去市场收益率,得到的就是「超额收益」。但这里有个细节:是用减法还是用除法?

我个人习惯用减法,因为减法得到的是「绝对超额」,而除法得到的是「相对超额」。在横截面比较时,减法更直观。但在时间序列上,除法可能更稳定。

# 相对强弱因子构造
import pandas as pd
import numpy as np

# 模拟数据
stock_returns = pd.Series([0.03, -0.01, 0.02, 0.05, -0.02])
market_returns = pd.Series([0.01, 0.00, 0.01, 0.02, -0.01])

# 方法1:减法(绝对超额)
excess_abs = stock_returns - market_returns
print("绝对超额:", excess_abs.values)
# 输出:[0.02, -0.01, 0.01, 0.03, -0.01]

# 方法2:除法(相对超额)
excess_rel = stock_returns / (market_returns + 1e-8)
print("相对超额:", excess_rel.values)
# 输出:[3.0, -inf, 2.0, 2.5, 2.0] —— 注意第二个值因为市场收益为0,出现异常

# 方法3:对数差分(更稳定)
excess_log = np.log(1 + stock_returns) - np.log(1 + market_returns)
print("对数超额:", excess_log.values)
# 输出:[0.0198, -0.0100, 0.0099, 0.0294, -0.0100]
小技巧:我个人更推荐用对数差分来做相对强弱。因为对数收益率具有可加性,而且能避免除零问题。在实盘中,对数差分构造的因子稳定性更好。

4.7 双变量特征的知识体系

说了这么多,我们来画个图,把双变量特征构造的整个知识体系梳理一下。

双变量特征构造知识体系 双变量特征 减法 (A - B) 除法 (A / B) 乘法 (A × B) 均线差 价差 市盈率 夏普比率 量价配合 核心原则 1. 先问经济含义,再谈数学运算 2. 注意量纲统一和除零问题 3. 标准化后再做加法/乘法,避免量纲主导

4.8 避坑指南与经验总结

最后,我把自己这些年踩过的坑总结一下,希望对你有帮助。

  • 不要盲目组合:两个弱相关因子组合,不一定能得到强因子。我见过有人把换手率和市盈率做比值,结果毫无意义。先想清楚经济逻辑。
  • 注意多重共线性:如果你用A和B构造了A-B,又用A和C构造了A-C,那么A-B和A-C之间可能存在共线性。做回归时要注意。
  • 小心极端值:比率特征容易产生极端值,尤其是分母很小的时候。我建议做winsorize处理,把极端值截断到99%分位数。
  • 回测要严谨:双变量特征在回测中容易过拟合。我建议做交叉验证,或者用样本外数据验证。

好了,这一章就到这里。双变量特征构造,说白了就是「1+1>2」的艺术。但记住,不是所有的1+1都能大于2,关键看你怎么组合。下一章我们会聊更高阶的特征构造方法,敬请期待。


公众号:蓝海资料掘金营,微信deep3321