4. 成交量分布(VPIN)模型:基于成交量的指令流毒性指标构建
各位同学,今天我们来聊一个在微观结构里非常核心的概念——VPIN。
VPIN,全称是 Volume-synchronized Probability of Informed Trading。翻译过来就是“成交量同步的知情交易概率”。
说白了,它衡量的是:当前这笔成交量里,到底有多少是“聪明钱”在动手?
我最早接触VPIN是在2015年,当时国内股指期货市场波动剧烈。我尝试用传统PIN模型去抓异常,结果发现计算太慢,根本跟不上行情。后来看到Easley等人提出的VPIN,才觉得“嗯,这才是实战该用的东西”。
4.1 为什么需要VPIN?
传统的PIN模型(Probability of Informed Trading)需要估计订单流的买卖不平衡参数。它依赖最大似然估计,计算量很大,而且对数据频率敏感。
VPIN不一样。它直接基于成交量来划分时间区间,而不是固定时间窗口。你想想看,市场在活跃时段每秒成交几千笔,在冷清时段可能几秒才一笔。用固定时间窗口去算,信息密度完全不一样。
VPIN的核心思想是:把成交量分成等量的小篮子(buckets),每个篮子里的成交量相等,然后看这个篮子里买卖方向的不平衡程度。
关键区别:
- PIN:基于时间窗口,计算复杂
- VPIN:基于成交量窗口,计算简单,实时性强
4.2 VPIN的计算步骤
VPIN的计算其实不复杂,但有几个细节容易踩坑。我一步步说清楚。
第一步:确定成交量篮子大小 V
V 是每个篮子包含的成交量。通常取过去一段时间日均成交量的 1/50 或 1/100。比如某股票日均成交 1000 万股,那 V 可以取 20 万股。
我个人习惯用过去20天的平均成交量来动态调整 V,这样能适应市场活跃度的变化。
第二步:将逐笔成交数据按时间顺序填充到篮子里
每个篮子填满 V 股后,就关闭这个篮子,开始下一个。注意,最后一笔成交可能跨篮子,需要拆分。
第三步:计算每个篮子的买卖方向
这里需要用到“成交量标记法”(Volume-based Tick Rule)。
简单说:
- 如果当前价格高于上一笔价格,标记为买方主动(Buy)
- 如果低于上一笔,标记为卖方主动(Sell)
- 如果价格不变,沿用上一笔的标记
然后,每个篮子的买卖不平衡量就是:
OI = |V_buy - V_sell| / V
其中 V_buy 和 V_sell 分别是该篮子中买方和卖方成交量。
第四步:计算 VPIN
取最近 n 个篮子的 OI 平均值,就是 VPIN 值。
VPIN = (1/n) * Σ OI_i
n 通常取 50 或 100。我一般取 50,因为对短期信号更敏感。
4.3 代码实现
下面是一个完整的 VPIN 计算函数。我尽量写得简洁,但保留了关键细节。
import pandas as pd
import numpy as np
def compute_vpin(trade_data, bucket_volume=200000, n_buckets=50):
"""
计算 VPIN 指标
参数:
trade_data: DataFrame,必须包含 'price' 和 'volume' 列
bucket_volume: 每个篮子的目标成交量
n_buckets: 用于计算 VPIN 的篮子数量
返回:
vpin_series: VPIN 时间序列
"""
# 1. 标记买卖方向(Tick Rule)
trade_data['side'] = 0
trade_data.loc[trade_data['price'] > trade_data['price'].shift(1), 'side'] = 1 # Buy
trade_data.loc[trade_data['price'] < trade_data['price'].shift(1), 'side'] = -1 # Sell
trade_data['side'] = trade_data['side'].fillna(method='ffill')
# 2. 按成交量分组到篮子
trade_data['cum_vol'] = trade_data['volume'].cumsum()
trade_data['bucket_id'] = (trade_data['cum_vol'] // bucket_volume).astype(int)
# 3. 计算每个篮子的买卖不平衡
bucket_agg = trade_data.groupby('bucket_id').agg(
buy_vol=('volume', lambda x: x[trade_data.loc[x.index, 'side'] == 1].sum()),
sell_vol=('volume', lambda x: x[trade_data.loc[x.index, 'side'] == -1].sum()),
total_vol=('volume', 'sum')
)
bucket_agg['oi'] = np.abs(bucket_agg['buy_vol'] - bucket_agg['sell_vol']) / bucket_agg['total_vol']
# 4. 计算 VPIN(滚动平均)
bucket_agg['vpin'] = bucket_agg['oi'].rolling(window=n_buckets, min_periods=1).mean()
return bucket_agg['vpin']
避坑指南:
我曾经在实盘回测时发现 VPIN 值突然跳变,排查了半天才发现是 Tick Rule 标记在开盘第一笔数据时出了问题。第一笔没有上一笔价格,side 会变成 NaN。记得用 fillna 处理,或者直接标记为 0(中性)。
4.4 VPIN 的解读与应用
VPIN 的值域在 0 到 1 之间。数值越高,说明当前成交量中买卖方向越不平衡,指令流毒性越强。
| VPIN 区间 | 含义 | 操作建议 |
|---|---|---|
| 0 - 0.3 | 指令流毒性低,市场均衡 | 正常交易,无需特殊处理 |
| 0.3 - 0.6 | 中等毒性,存在信息不对称 | 降低订单规模,增加监控 |
| 0.6 - 0.8 | 高毒性,可能有重大信息 | 暂停大单,改用冰山订单 |
| 0.8 - 1.0 | 极端毒性,市场可能剧烈波动 | 建议暂停交易,等待信息释放 |
我在做高频做市商策略时,会把 VPIN 作为风控指标。当 VPIN 超过 0.7 时,自动降低报价深度,减少被“聪明钱”吃掉的概率。
4.5 VPIN 的局限性
VPIN 不是万能的。我遇到过几个问题:
- 篮子大小选择敏感:V 太小,噪声大;V 太大,信号滞后。需要根据品种流动性调整。
- Tick Rule 在零股交易时容易误判:价格不变但成交方向可能不同,这时候沿用上一笔标记会引入误差。
- 无法区分信息驱动和流动性驱动:有时候大额买卖只是因为机构调仓,并非信息交易。
注意:VPIN 更适合用于期货、ETF 等流动性好的品种。对于成交稀疏的股票,篮子可能很久都填不满,导致指标失效。
4.6 知识体系图
下面这张图总结了 VPIN 的核心逻辑和计算流程:
4.7 实战中的一点体会
VPIN 这个指标,说实话,单独用效果一般。我一般会结合订单簿的深度变化一起看。
举个例子:如果 VPIN 突然升高,同时买一卖一价差也在扩大,那基本可以确定有大资金在动手。这时候我的策略会立刻切换到“防御模式”——要么撤单,要么缩小报价规模。
反过来,如果 VPIN 升高但价差稳定,可能只是短期流动性冲击,不用太紧张。
核心要点回顾:
- VPIN 用成交量篮子代替时间窗口,计算效率高
- 核心是买卖方向标记和滚动平均
- 高 VPIN 意味着指令流毒性强,需要谨慎
- 结合其他微观结构指标使用效果更好
好了,VPIN 的内容就到这里。这个指标虽然简单,但在实战中非常实用。建议你拿自己的数据跑一遍,感受一下不同市场状态下的 VPIN 变化。