3、VWAP策略的经典拆分逻辑:时间切片与成交量分布模型

做VWAP执行,最核心的问题就一个:怎么把大单拆成小单,跟着市场的节奏走

我刚开始做算法交易那会儿,总觉得VWAP很简单——不就是按时间平均分嘛。结果实盘跑起来,偏差大得吓人。后来才明白,时间切片只是基础,真正的精髓在成交量分布模型里

今天咱们就把这两个核心逻辑拆开揉碎了讲清楚。

3.1 时间切片:最朴素的拆分方式

时间切片,说白了就是把交易时段切成N等份,每份分配等量的订单。

举个例子:你要在4小时内买入100万股。如果切成240个1分钟切片,每个切片就分配约4167股。

代码实现很简单:

def time_slice(total_qty, start_time, end_time, slice_seconds=60):
    """
    时间切片拆分
    total_qty: 总成交量
    start_time/end_time: 交易时段
    slice_seconds: 切片长度(秒)
    """
    total_seconds = (end_time - start_time).total_seconds()
    num_slices = int(total_seconds / slice_seconds)
    
    slice_qty = total_qty / num_slices
    
    slices = []
    for i in range(num_slices):
        slice_time = start_time + timedelta(seconds=i * slice_seconds)
        slices.append({
            'time': slice_time,
            'qty': slice_qty
        })
    
    return slices

嗯,这里要注意——时间切片有个致命缺陷:它假设成交量在时间上是均匀分布的。但真实市场根本不是这样。

我遇到过最惨的一次,上午10点那波放量,我的切片量根本不够用,眼睁睁看着价格往上冲。到了下午缩量阶段,我又被迫把剩下的单子砸进去,直接把价格打穿了。

所以,时间切片只适合做基准,不能直接用于实盘

3.2 成交量分布模型:这才是VWAP的灵魂

VWAP的全称是Volume-Weighted Average Price。你想想看,既然要按成交量加权,那拆分逻辑当然也要跟着成交量走。

核心思想很简单:成交量大的时段多分单,成交量小的时段少分单

怎么做?需要先预测每个时间片的成交量占比。

3.2.1 历史成交量分布曲线

我习惯用过去20个交易日的分钟级成交量数据,计算每个时间片的平均占比。

def volume_profile(hist_data, slice_seconds=60):
    """
    计算历史成交量分布
    hist_data: DataFrame,包含timestamp和volume列
    """
    # 提取交易时段(假设9:30-15:00)
    market_open = pd.Timestamp('09:30:00').time()
    market_close = pd.Timestamp('15:00:00').time()
    
    # 过滤交易时段数据
    mask = (hist_data['timestamp'].dt.time >= market_open) & \
           (hist_data['timestamp'].dt.time <= market_close)
    trading_data = hist_data[mask].copy()
    
    # 按时间切片分组
    trading_data['time_bin'] = trading_data['timestamp'].dt.floor(f'{slice_seconds}S')
    
    # 计算每个切片的成交量占比
    profile = trading_data.groupby('time_bin')['volume'].sum()
    profile = profile / profile.sum()
    
    return profile

这张图就是典型的A股成交量分布曲线:

典型特征:

  • 开盘前30分钟:成交量最大,占比约20%-25%
  • 上午10:30-11:30:成交量逐渐萎缩
  • 下午开盘13:00-13:30:有一波小放量
  • 尾盘14:30-15:00:再次放量,占比约15%-18%

我用SVG画了一张成交量分布曲线图,方便你直观理解:

典型A股日内成交量分布曲线 9:30 10:30 11:30/13:00 14:00 15:00 0% 1% 2% 开盘放量 午间休市 尾盘放量

3.2.2 实时成交量修正

光靠历史数据不够。市场每天都在变,必须引入实时修正机制

我常用的方法是:

def adaptive_vwap_split(total_qty, current_time, hist_profile, real_volume, alpha=0.7):
    """
    自适应VWAP拆分
    alpha: 历史权重(0-1),越大越依赖历史数据
    """
    # 获取当前时间片的预期占比
    expected_pct = hist_profile.get(current_time, 0)
    
    # 获取已实现的成交量占比
    realized_pct = real_volume / total_qty if total_qty > 0 else 0
    
    # 动态调整:如果实际成交量已经偏大,就减少后续分配
    adjustment = 1 - (realized_pct - expected_pct) * 2
    
    # 限制调整幅度
    adjustment = max(0.5, min(1.5, adjustment))
    
    # 计算当前切片应分配的量
    slice_qty = total_qty * expected_pct * adjustment
    
    return slice_qty

实战技巧:

alpha值怎么调?我一般这样设置:

  • 开盘前30分钟:alpha=0.8(历史数据更可靠)
  • 盘中:alpha=0.6(实时数据更重要)
  • 尾盘:alpha=0.7(折中处理)

3.3 两种模型的对比

维度 时间切片 成交量分布模型
核心逻辑 按时间均匀分配 按成交量加权分配
VWAP跟踪精度 低(偏差通常>0.5%) 高(偏差可控制在0.1%以内)
对市场冲击 大(缩量时硬塞) 小(跟随流动性)
实现复杂度 简单 中等
适用场景 小单、流动性极好的股票 大单、流动性一般的股票

3.4 我踩过的坑

曾经有一次,我过于依赖历史成交量分布,没考虑当天有重大利好公告。结果开盘成交量是平时的3倍,我的模型还在按历史数据慢慢分配,导致大量订单没来得及成交,VWAP偏差直接爆表。

从那以后,我加了一个异常检测模块

def anomaly_detection(current_volume, hist_mean, hist_std, threshold=3):
    """
    成交量异常检测
    如果当前成交量超过历史均值±3倍标准差,触发调整
    """
    z_score = (current_volume - hist_mean) / hist_std
    
    if abs(z_score) > threshold:
        return True, z_score
    return False, z_score

避坑指南:

我曾经吃过亏的几个点:

  • 不要用全部历史数据,只用最近20-30个交易日
  • 剔除异常交易日(如分红、除权日)的数据
  • 遇到停牌复牌,成交量分布会完全失效,需要特殊处理

3.5 实战中的选择建议

说白了,没有完美的模型,只有适合的模型。

我个人建议:

  • 小单(<总成交量1%):直接用时间切片就行,省事
  • 中单(1%-5%):用成交量分布模型,alpha设0.7
  • 大单(>5%):必须用成交量分布模型,还要加上实时修正和异常检测

嗯,这里再补充一句——模型再好,也抵不过市场突变。所以一定要留一手:当模型预测和实际偏差超过阈值时,果断切换到保守模式,宁可少成交,也别把价格打飞了。

这就是VWAP拆分的核心逻辑。你想想看,其实没那么复杂——跟着成交量走,别跟市场对着干


公众号:蓝海资料掘金营,微信deep3321