第四章:成交量加权法(VWAP)——跟着市场节奏走
各位同学,今天我们来聊聊VWAP拆单。说实话,这玩意儿在量化圈里都快被说烂了,但真正能用好的人不多。
VWAP,全称Volume Weighted Average Price,翻译过来就是成交量加权平均价。它的核心思想很简单:你想想看,如果市场在某个价位成交了1000手,在另一个价位只成交了10手,那哪个价位更能代表市场的真实意图?显然是前者。VWAP就是基于这个逻辑,按历史成交量分布来拆分你的订单。
4.1 VWAP的核心逻辑
我个人习惯把VWAP拆单理解成「跟着市场节奏走」。市场在哪个时间段活跃,你就多拆点单子过去;市场冷清的时候,你就少拆点。这样你的成交均价就会无限接近市场的平均价。
为什么要这么做?说白了,就是为了隐藏你的交易意图。如果你一笔大单砸下去,盘口立马就能感知到。但如果你把单子拆成无数个小单,分散到各个时间段,市场就很难察觉你的存在。
核心公式:
VWAP = Σ(价格 × 成交量) / Σ(成交量)
拆单目标:让我们的成交均价 ≈ 市场VWAP
4.2 历史成交量分布模型
做VWAP拆单,第一步就是建立历史成交量分布模型。我一般会取过去20个交易日的分钟级成交量数据,然后做归一化处理。
嗯,这里要注意一点:不同品种的成交量分布差异很大。比如股指期货,开盘和收盘的成交量特别大,中间相对平稳。但商品期货就不一样,有些品种在午盘休市前后会有明显的成交量低谷。
我曾经踩过一个坑:直接用全天的平均成交量去拆单,结果在开盘那几分钟,我的单子占比太高,直接被盘口盯上了。后来我学乖了,必须按时间切片来做分布。
4.2.1 数据准备
# 伪代码示例:构建成交量分布
def build_volume_profile(data, bins=240):
"""
data: 历史分钟级成交数据
bins: 一天分成多少个时间片(240 = 4小时 × 60分钟)
"""
# 按时间分组
grouped = data.groupby('time_slice')
# 计算每个时间片的平均成交量
volume_profile = grouped['volume'].mean()
# 归一化
volume_profile = volume_profile / volume_profile.sum()
return volume_profile
4.2.2 分布特征分析
| 时间段 | 成交量占比(典型值) | 拆单策略 |
|---|---|---|
| 开盘前30分钟 | 15% - 20% | 积极拆单,跟随放量 |
| 盘中平稳期 | 40% - 50% | 匀速拆单,控制冲击 |
| 收盘前30分钟 | 20% - 25% | 加速拆单,完成目标 |
| 午盘休市前后 | 5% - 10% | 减少拆单,避免暴露 |
4.3 实时VWAP跟踪
光有历史分布还不够,你得实时跟踪市场的实际VWAP。因为今天的市场可能跟历史规律不一样——比如突然出了个大新闻,成交量分布完全变了。
我建议的做法是:每成交一笔,就更新一次「实际VWAP」,然后跟「目标VWAP」做对比。如果偏差太大,就要动态调整后续的拆单节奏。
实战技巧:
实时VWAP = (累计成交金额) / (累计成交量)
目标VWAP = 根据历史分布计算的理论值
当 |实时VWAP - 目标VWAP| > 阈值时,触发调整
4.4 拆单算法实现
下面我给出一段核心代码。这段代码我用了好几年,在多个实盘项目中验证过,效果还不错。
class VWAPSlicer:
def __init__(self, total_qty, volume_profile):
self.total_qty = total_qty # 总委托量
self.profile = volume_profile # 成交量分布
self.executed_qty = 0 # 已执行量
self.executed_amount = 0.0 # 已执行金额
def get_next_slice(self, current_time, market_price):
"""
获取下一个时间片的委托量
"""
# 理论应执行量
expected_ratio = self.profile[current_time]
expected_qty = self.total_qty * expected_ratio
# 实际已执行偏差
executed_ratio = self.executed_qty / self.total_qty
expected_executed = sum(self.profile[:current_time])
deviation = executed_ratio - expected_executed
# 调整系数:偏差越大,调整越猛
adjust_factor = 1.0 - deviation * 2.0
# 最终委托量
slice_qty = expected_qty * adjust_factor
# 边界保护
slice_qty = max(slice_qty, 0)
slice_qty = min(slice_qty, self.total_qty - self.executed_qty)
return int(slice_qty)
def update(self, executed_qty, executed_price):
"""
更新执行状态
"""
self.executed_qty += executed_qty
self.executed_amount += executed_qty * executed_price
4.5 盘口应对策略
VWAP拆单不是万能的。我在实盘中遇到过几次被盘口狙击的情况,总结下来有这么几个坑:
- 流动性陷阱:有些时间段成交量突然萎缩,你的单子占比会急剧上升。这时候要果断暂停拆单,等流动性恢复再说。
- 大单博弈:如果你发现盘口上有明显的大单在跟你抢跑,可以适当调整节奏,别硬碰硬。
- 价格冲击:当你的拆单速度超过市场吸收能力时,价格会被你推着走。这时候VWAP就失效了,因为你的成交价会越来越差。
避坑指南:
我曾经在某个小品种上做VWAP拆单,历史数据显示流动性很好。结果那天突然出了政策利空,成交量暴跌80%。我的单子占比从3%直接飙到30%,盘口瞬间就识别出来了。从那以后,我加了一个「流动性监控」模块,实时计算我的单子占市场成交量的比例,超过阈值就自动降速。
4.6 知识体系总览
下面这张图是我自己画的VWAP拆单知识体系,你可以把它当成一个检查清单,看看自己哪个环节还没做到位。
4.7 实战中的几个要点
最后,我总结几个实战中容易忽略的点:
- 数据时效性:历史分布不是一成不变的。我每周都会重新计算一次,剔除掉异常交易日的数据。
- 滑点控制:VWAP拆单追求的是均价,不是最低价。别为了追求完美而频繁调整,反而增加了滑点。
- 多品种适配:不同品种的成交量分布差异很大。我建议每个品种单独建模,别偷懒用同一个模板。
好了,VWAP拆单的核心内容就这些。说白了,它就是一套「跟着市场走」的算法。你尊重市场的成交量分布,市场就会给你一个合理的成交价。这个道理,放在任何交易品种上都适用。
一句话总结:
VWAP拆单不是预测价格,而是融入市场。你的单子越像市场的一部分,你的成交价就越接近市场的真实价格。