第四章:成交量加权法(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拆单知识体系,你可以把它当成一个检查清单,看看自己哪个环节还没做到位。

VWAP拆单知识体系 数据层 历史分钟级成交数据 模型层 成交量分布归一化 执行层 实时拆单与调整 核心算法 偏差计算 → 调整系数 → 委托量生成 实时监控 流动性占比 VWAP偏差跟踪 风控规则 占比超限 → 降速 偏差过大 → 暂停 输出:接近市场VWAP的成交均价

4.7 实战中的几个要点

最后,我总结几个实战中容易忽略的点:

  1. 数据时效性:历史分布不是一成不变的。我每周都会重新计算一次,剔除掉异常交易日的数据。
  2. 滑点控制:VWAP拆单追求的是均价,不是最低价。别为了追求完美而频繁调整,反而增加了滑点。
  3. 多品种适配:不同品种的成交量分布差异很大。我建议每个品种单独建模,别偷懒用同一个模板。

好了,VWAP拆单的核心内容就这些。说白了,它就是一套「跟着市场走」的算法。你尊重市场的成交量分布,市场就会给你一个合理的成交价。这个道理,放在任何交易品种上都适用。

一句话总结:

VWAP拆单不是预测价格,而是融入市场。你的单子越像市场的一部分,你的成交价就越接近市场的真实价格。

专注资料整理