4、特征工程基础:价格序列特征、成交量特征、订单簿不平衡指标、波动率计算
好,咱们进入正题。特征工程这块,说白了就是给模型喂「有营养」的数据。你想想看,原始订单簿数据就像一堆乱码,模型根本看不懂。我刚开始做量化那会儿,也犯过傻——直接把原始数据扔进模型,结果预测效果惨不忍睹。后来才明白,特征工程才是预测模型的灵魂。
4.1 价格序列特征
价格序列特征,我习惯从三个维度去提取:原始价格、价格变化、价格形态。
4.1.1 原始价格特征
最基础的就是买卖一档价格。但注意,别只盯着最新成交价。我个人习惯把买一价、卖一价、最新成交价、开盘价、收盘价都纳入特征池。为什么?因为不同价格反映的市场信息不同。比如买一价代表买方意愿,卖一价代表卖方意愿,两者差距就是市场分歧。
- 买一价 (bid_price_1)
- 卖一价 (ask_price_1)
- 最新成交价 (last_price)
- 当日开盘价 (open_price)
- 前N笔交易均价 (vwap_N)
4.1.2 价格变化特征
价格本身没意义,价格变化才有。我常用的是价格差分和价格变化率。举个例子:
# 价格差分(一阶差分)
df['price_diff'] = df['last_price'].diff()
# 价格变化率(对数收益率)
df['log_return'] = np.log(df['last_price'] / df['last_price'].shift(1))
# 价格变化率(百分比)
df['pct_return'] = df['last_price'].pct_change()
这里有个坑:对数收益率比百分比收益率更稳定。我在做高频策略时,发现对数收益率的时间序列统计特性更好,尤其适合做波动率建模。
4.1.3 价格形态特征
除了数值,价格形态也很重要。我常用移动平均线、布林带、RSI等技术指标。但注意,别一股脑全用上。我建议只选3-5个最有效的。
# 移动平均线
df['ma_5'] = df['last_price'].rolling(window=5).mean()
df['ma_20'] = df['last_price'].rolling(window=20).mean()
# 布林带
df['bb_mid'] = df['last_price'].rolling(window=20).mean()
df['bb_std'] = df['last_price'].rolling(window=20).std()
df['bb_upper'] = df['bb_mid'] + 2 * df['bb_std']
df['bb_lower'] = df['bb_mid'] - 2 * df['bb_std']
4.2 成交量特征
成交量是市场的「燃料」。没有成交量的价格变动,就像没有观众的表演——不可持续。我总结成交量特征有三个核心维度:绝对量、相对量、形态。
4.2.1 绝对成交量特征
最直接的就是买卖一档的挂单量。但注意,挂单量不等于成交量。挂单量是「意愿」,成交量是「行动」。我习惯把两者都纳入特征。
- 买一量 (bid_volume_1)
- 卖一量 (ask_volume_1)
- 最近N笔成交量 (volume_N)
- 累计成交量 (cum_volume)
4.2.2 相对成交量特征
绝对量受时间影响大,相对量更有意义。我常用成交量变化率和成交量与均值的比值。
# 成交量变化率
df['volume_change'] = df['volume'].pct_change()
# 成交量与20期均值的比值
df['volume_ratio'] = df['volume'] / df['volume'].rolling(window=20).mean()
嗯,这里要注意:成交量比值大于1.5时,往往意味着市场情绪异常。我在实盘中遇到过,当比值超过2.0时,价格反转的概率显著增加。
4.2.3 成交量形态特征
成交量也有形态。比如放量上涨、缩量下跌这些经典形态。我习惯用成交量与价格的协动关系来刻画。
# 成交量与价格的相关性(滚动窗口)
df['vol_price_corr'] = df['volume'].rolling(window=10).corr(df['last_price'])
4.3 订单簿不平衡指标
这个指标是我个人非常看重的。订单簿不平衡,说白了就是买卖双方的力量对比。你想想看,如果买一档有1000手,卖一档只有100手,那价格大概率要往上走。
4.3.1 经典不平衡指标
最常用的就是订单簿不平衡率 (Order Book Imbalance, OBI):
# 订单簿不平衡率
df['obi'] = (df['bid_volume_1'] - df['ask_volume_1']) / (df['bid_volume_1'] + df['ask_volume_1'])
OBI的取值范围是[-1, 1]。正值表示买方力量强,负值表示卖方力量强。绝对值越大,力量越悬殊。
4.3.2 深度加权不平衡指标
经典OBI只考虑一档,太浅了。我建议用深度加权不平衡指标,把多档价格和量都考虑进去。
# 深度加权不平衡(前5档)
def depth_weighted_imbalance(df, levels=5):
bid_vol = 0
ask_vol = 0
for i in range(1, levels+1):
bid_vol += df[f'bid_volume_{i}'] / i
ask_vol += df[f'ask_volume_{i}'] / i
return (bid_vol - ask_vol) / (bid_vol + ask_vol)
df['dwi'] = depth_weighted_imbalance(df)
4.3.3 价格加权不平衡指标
还有一种思路:把价格也加权进去。毕竟,离当前价格越近的档位,影响力越大。
# 价格加权不平衡
def price_weighted_imbalance(df, levels=5):
bid_vol = 0
ask_vol = 0
for i in range(1, levels+1):
bid_vol += df[f'bid_volume_{i}'] * df[f'bid_price_{i}']
ask_vol += df[f'ask_volume_{i}'] * df[f'ask_price_{i}']
return (bid_vol - ask_vol) / (bid_vol + ask_vol)
4.4 波动率计算
波动率是市场的「心跳」。没有波动率,价格预测就失去了意义。我常用的波动率计算方法有四种:历史波动率、已实现波动率、GARCH波动率、Parkinson波动率。
4.4.1 历史波动率
最简单,也最常用。就是过去N期的收益率标准差。
# 历史波动率(20期)
df['hist_vol'] = df['log_return'].rolling(window=20).std() * np.sqrt(252)
注意:乘以sqrt(252)是为了年化。如果是高频数据,要换成对应的交易频率。
4.4.2 已实现波动率
这个更精细。它利用日内高频数据计算,能捕捉到日内波动细节。
# 已实现波动率(5分钟频率)
df['rv_5min'] = df['log_return'].rolling(window=12).apply(lambda x: np.sqrt(np.sum(x**2)))
4.4.3 GARCH波动率
GARCH模型能捕捉波动率的聚集效应。说白了,就是大波动后面往往跟着大波动。
from arch import arch_model
# 拟合GARCH(1,1)模型
model = arch_model(df['log_return'].dropna(), vol='Garch', p=1, q=1)
results = model.fit(disp='off')
df['garch_vol'] = results.conditional_volatility
4.4.4 Parkinson波动率
这个利用了最高价和最低价的信息,比只用收盘价的历史波动率更高效。
# Parkinson波动率
def parkinson_vol(high, low, window=20):
log_hl = np.log(high / low)
return np.sqrt((1 / (4 * np.log(2))) * log_hl.rolling(window=window).mean())
df['park_vol'] = parkinson_vol(df['high'], df['low'])
4.5 特征工程知识体系
说了这么多,我画个图帮你理清思路。特征工程不是孤立的技术,而是一个完整的体系。
这张图把咱们讲的内容串起来了。你想想看,四个大类下面各有子特征,最终都服务于一个核心目标:让模型更好地理解市场微观结构。
好了,特征工程基础就讲到这里。记住:好的特征胜过复杂的模型。把基础打牢,后面的模型构建才能事半功倍。
公众号:蓝海资料掘金营,微信deep3321