3. 成交量分布:VPIN 与 VWAP
做高频交易的人,每天打交道最多的东西是什么?
不是K线,不是均线,是成交量。
成交量是市场的血液。没有成交量,价格就是死水一潭。今天我要聊的两个工具——VPIN(成交量序贯概率反转指标)和VWAP(成交量加权平均价格),就是用来解剖这管血液的。
3.1 先说说 VWAP:机构最爱的基准
VWAP 其实不复杂。说白了就是:用成交量做权重的平均价格。
公式长这样:
VWAP = Σ(价格 × 成交量) / Σ(成交量)
举个例子。假设某合约一天成交了3笔:
| 时间 | 价格 | 成交量 |
|---|---|---|
| 09:30 | 100 | 200 |
| 10:00 | 102 | 300 |
| 10:30 | 101 | 500 |
VWAP = (100×200 + 102×300 + 101×500) / (200+300+500) = 101.1
而简单均价是 (100+102+101)/3 = 101.0。差了一毛钱,但这一毛钱在百万级别的单子里,就是几万块的差距。
核心认知:VWAP 反映的是市场真实的成交成本。机构拆单时,目标就是让成交价尽量贴近 VWAP。低于 VWAP 买入,高于 VWAP 卖出,这就是 alpha 的来源。
我个人习惯把 VWAP 当作一条动态的「公允价格线」。价格在 VWAP 上方,说明买方力量强;在下方,卖方占优。但注意,这不是绝对的——我见过太多人把 VWAP 当支撑阻力用,结果被来回打脸。
3.2 VPIN:测一测市场的「毒性」
VPIN 这个名字听起来高大上,其实逻辑很直白:它衡量的是订单流的不平衡程度。
怎么算?把一天切成若干等量成交量的「桶」(bucket),每个桶里统计主动买和主动卖的差值。差值越大,说明这个桶里的订单流越「毒」——也就是方向性越强。
公式:
VPIN = Σ|主动买量 - 主动卖量| / (n × 桶大小)
其中 n 是桶的数量,桶大小通常取日成交量的 1/50 或 1/100。
VPIN 的值在 0 到 1 之间。值越高,说明订单流越不平衡,市场越容易发生反转。
实战经验:我曾经在螺纹钢期货上跑 VPIN,发现当 VPIN 超过 0.6 时,接下来 5 分钟内价格反转的概率超过 70%。但要注意,这个阈值在不同品种上差异很大——股指期货的 VPIN 通常比商品期货低一个数量级。
3.3 VPIN 和 VWAP 怎么配合用?
单看一个指标,就像只用一只眼看路。我一般这样搭配:
- VWAP 判断方向:价格在 VWAP 之上,偏多;之下,偏空。
- VPIN 判断时机:VPIN 高 + 价格远离 VWAP = 反转概率大。
- VPIN 低 + 价格贴近 VWAP = 震荡行情,别乱动。
举个例子。假设当前价格 101.5,VWAP 是 100.8,VPIN 飙到 0.7。这说明什么?
价格已经偏离了公允值,而且订单流极度不平衡。这时候追多?我劝你冷静。大概率是主力在拉高出货。我一般会等 VPIN 回落,或者价格回到 VWAP 附近再动手。
避坑指南:我曾经犯过一个错——在 VPIN 极高时直接反向开仓。结果市场继续朝原方向猛冲,把我打止损了。后来我加了一条规则:必须等 VPIN 从高位回落 20% 以上,才确认反转信号有效。这叫「等子弹飞一会儿」。
3.4 代码实现:手撸一个 VPIN
理论说完了,上代码。这是我在实盘中用过的简化版:
import pandas as pd
import numpy as np
def compute_vpin(trade_data, bucket_volume=1000, n_buckets=50):
"""
trade_data: DataFrame,必须包含 'price', 'volume', 'side' 三列
side: 1 表示主动买,-1 表示主动卖
"""
# 按时间排序
trade_data = trade_data.sort_values('time')
# 计算累计成交量
trade_data['cum_vol'] = trade_data['volume'].cumsum()
# 按桶分组
trade_data['bucket'] = trade_data['cum_vol'] // bucket_volume
# 每个桶内计算净成交量
bucket_stats = trade_data.groupby('bucket').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())
)
# 计算 VPIN
bucket_stats['imbalance'] = abs(bucket_stats['buy_vol'] - bucket_stats['sell_vol'])
vpin = bucket_stats['imbalance'].rolling(n_buckets).sum() / (n_buckets * bucket_volume)
return vpin
这段代码的核心就两步:切桶、算不平衡。实际生产中,桶大小要根据品种的流动性动态调整。比如螺纹钢一天成交 200 万手,桶取 2 万手比较合适;而沪深 300 股指期货一天 20 万手,桶取 2000 手就够了。
3.5 一张图看懂本章逻辑
下面这张 SVG 图,把 VPIN 和 VWAP 的关系画清楚了:
你看,VWAP 和 VPIN 就像市场的两只眼睛。一只看位置,一只看力度。两只眼睛一起睁开,才能看清市场的真实意图。
3.6 几个容易踩的坑
- 桶大小别乱设:太小了噪声大,太大了信号滞后。我一般用日成交量的 1/50 作为初始值,然后根据回测结果微调。
- VPIN 不是万能的:在趋势行情里,VPIN 会持续高位,这时候做反转就是接飞刀。一定要结合趋势过滤。
- VWAP 要区分周期:日内 VWAP 和日线 VWAP 含义不同。高频交易看日内 VWAP,中低频看日线 VWAP。
- 数据源要干净:主动买/主动卖的判断依赖逐笔数据。如果交易所给的 side 字段不准,VPIN 就是垃圾进垃圾出。
一个小技巧:我习惯把 VPIN 和 VWAP 画在同一个副图上。价格线、VWAP 线、VPIN 柱状图叠在一起,一眼就能看出什么时候该动手。用 Python 的 matplotlib 就能搞定,代码量不超过 20 行。
好了,这一章就到这里。VPIN 和 VWAP 是微观结构分析里最基础也最实用的两个工具。别嫌它们简单——我见过太多人把复杂模型跑得飞起,结果连 VWAP 都没算对。基础打牢了,后面讲订单簿重建、限价单簿动态博弈的时候,你才能跟得上。