14、订单流与成交量结合:成交量加权平均价格(VWAP)的订单流版本
VWAP,成交量加权平均价格,是机构交易员最常用的基准之一。传统VWAP的计算方式很简单——把每一笔成交的价格乘以成交量,再除以总成交量。但这里有个问题:传统VWAP只看结果,不看过程。
什么意思呢?举个例子,某只股票在10块钱成交了1000手,又在10.01成交了500手。传统VWAP会告诉你均价是10.0033。但它不会告诉你,这1000手是被大单砸下来的,还是散户自然成交的。说白了,它丢失了订单流的信息。
我个人习惯把订单流数据融合进VWAP。这样算出来的VWAP,能反映真实的买卖压力。今天我们就来聊聊这个。
传统VWAP的局限性
先看传统VWAP的公式:
VWAP = Σ(价格 × 成交量) / Σ(成交量)
这个公式有个隐含假设:每一笔成交都是平等的。但做过实盘的人都知道,主动买和主动卖,意义完全不同。
我记得有一次做股指期货的日内策略,传统VWAP显示价格在均线上方,按理说应该偏多。但订单流数据显示,主动卖单的成交量远大于主动买单。结果呢?价格很快就掉下来了。从那以后,我再也不单独看传统VWAP了。
订单流VWAP的核心思想
订单流VWAP,说白了就是把成交拆成两部分:
- 主动买成交量:以卖一价及以上成交的,视为买方主动
- 主动卖成交量:以买一价及以下成交的,视为卖方主动
然后分别计算买方VWAP和卖方VWAP。两者的差值,就是订单流给我们的额外信息。
核心公式:
买方VWAP = Σ(主动买价格 × 主动买成交量) / Σ(主动买成交量)
卖方VWAP = Σ(主动卖价格 × 主动卖成交量) / Σ(主动卖成交量)
订单流VWAP差值 = 买方VWAP - 卖方VWAP
当买方VWAP高于卖方VWAP时,说明买方在更高的价位上持续买入,这是强势信号。反过来,就是弱势信号。
Python实现:从Tick数据到订单流VWAP
下面是我在实际项目中用过的代码框架。先声明一下,这里用的是简化版数据,真实场景下你需要处理Level2行情。
import pandas as pd
import numpy as np
def order_flow_vwap(tick_data):
"""
计算订单流VWAP
参数:
tick_data: DataFrame,必须包含列:
- price: 成交价格
- volume: 成交量
- side: 'buy' 或 'sell',表示主动买卖方向
返回:
DataFrame,包含买方VWAP、卖方VWAP和差值
"""
# 分离主动买和主动卖
buy_ticks = tick_data[tick_data['side'] == 'buy'].copy()
sell_ticks = tick_data[tick_data['side'] == 'sell'].copy()
# 计算累计值
buy_ticks['price_vol'] = buy_ticks['price'] * buy_ticks['volume']
sell_ticks['price_vol'] = sell_ticks['price'] * sell_ticks['volume']
buy_ticks['cum_vol'] = buy_ticks['volume'].cumsum()
buy_ticks['cum_price_vol'] = buy_ticks['price_vol'].cumsum()
sell_ticks['cum_vol'] = sell_ticks['volume'].cumsum()
sell_ticks['cum_price_vol'] = sell_ticks['price_vol'].cumsum()
# 计算VWAP
buy_ticks['vwap'] = buy_ticks['cum_price_vol'] / buy_ticks['cum_vol']
sell_ticks['vwap'] = sell_ticks['cum_price_vol'] / sell_ticks['cum_vol']
# 合并结果
result = pd.DataFrame(index=tick_data.index)
result['buy_vwap'] = buy_ticks['vwap']
result['sell_vwap'] = sell_ticks['vwap']
result['vwap_diff'] = result['buy_vwap'] - result['sell_vwap']
return result
# 使用示例
# tick_data = pd.read_csv('tick_data.csv')
# vwap_result = order_flow_vwap(tick_data)
# print(vwap_result.tail())
实战小技巧:
我一般不会直接用原始tick数据。因为tick数据太嘈杂了。我会先按1分钟或5分钟聚合,再计算订单流VWAP。这样信号更稳定,回测效果也更好。
如何判断主动买卖方向
这里有个关键问题:怎么判断一笔成交是主动买还是主动卖?
在Level2行情中,每笔成交都会带一个标识。但如果你拿不到这个标识,可以用一个近似方法:
- 如果成交价 >= 卖一价,视为主动买
- 如果成交价 <= 买一价,视为主动卖
- 如果成交价在买卖价之间,按比例分配
嗯,这里要注意。第二种情况在实盘中经常出现,尤其是流动性差的股票。我曾经因为这个分配比例的问题,回测和实盘对不上。后来我改成:如果价格在中间,就按最近10笔的买卖比例来分配。效果好了不少。
订单流VWAP的实战应用
我个人最常用的场景有两个:
- 趋势确认:当价格突破某个关键价位时,看订单流VWAP差值是否同步放大。如果价格涨了但差值在缩小,说明突破可能是假的。
- 背离信号:价格创新高,但买方VWAP没有创新高。这就是典型的顶背离。我在做期货日内时,这个信号的成功率大概在65%左右。
避坑指南:
我曾经犯过一个错误:在开盘前15分钟直接使用订单流VWAP。开盘时的成交量虽然大,但很多是噪音——集合竞价的单子、程序化试单等等。建议至少等开盘30分钟后再用。
可视化:订单流VWAP与价格的关系
下面这张图展示了订单流VWAP的核心逻辑。我习惯把买方VWAP和卖方VWAP画成两条线,再叠加价格K线。当两条线交叉时,往往意味着趋势的转折。
进阶:动态权重VWAP
上面讲的是基础版本。在实际项目中,我还用过一种改进版——动态权重VWAP。核心思路是:越近的成交,权重越大。
为什么?因为市场是动态的。10分钟前的成交,对当前价格的参考意义远不如最近1分钟的成交。我一般用指数衰减权重:
def dynamic_order_flow_vwap(tick_data, decay_factor=0.95):
"""
动态权重订单流VWAP
参数:
tick_data: DataFrame
decay_factor: 衰减因子,越小衰减越快
返回:
DataFrame
"""
weights = np.array([decay_factor ** i for i in range(len(tick_data))])
weights = weights[::-1] # 最近的权重最大
weights = weights / weights.sum() # 归一化
buy_mask = tick_data['side'] == 'buy'
sell_mask = tick_data['side'] == 'sell'
buy_vwap = np.sum(
tick_data.loc[buy_mask, 'price'] *
tick_data.loc[buy_mask, 'volume'] *
weights[buy_mask]
) / np.sum(tick_data.loc[buy_mask, 'volume'] * weights[buy_mask])
sell_vwap = np.sum(
tick_data.loc[sell_mask, 'price'] *
tick_data.loc[sell_mask, 'volume'] *
weights[sell_mask]
) / np.sum(tick_data.loc[sell_mask, 'volume'] * weights[sell_mask])
return buy_vwap, sell_vwap, buy_vwap - sell_vwap
参数调优建议:
衰减因子我一般设在0.95到0.99之间。股票用0.98,期货用0.95。因为期货波动更快,需要更灵敏的响应。你可以根据自己的品种做回测,找到最优参数。
总结一下
订单流VWAP不是要替代传统VWAP,而是给它加了一个维度——买卖方向。你想想看,同样的成交量,主动买和主动卖的意义完全不同。忽略这个信息,就像只看K线不看成交量一样,会错过很多关键信号。
我个人建议,刚开始可以先在模拟盘上跑一跑。把订单流VWAP和传统VWAP画在一起,观察它们的差异。慢慢地,你会找到感觉。
嗯,今天就聊到这里。代码部分可以直接拿去用,但记得根据你的数据格式做调整。