问题描述
解决方案
在 QMT(Quantitative Market Trading)策略中计算实时 VWAP(成交量加权平均价),最准确且高效的方法是利用Tick 数据中的累计成交额和累计成交量进行计算。
核心逻辑
VWAP 的定义是:
$$ \text{VWAP} = \frac{\text{当日累计成交额 (Total Amount)}}{\text{当日累计成交量 (Total Volume)}} $$
在 QMT 的 ContextInfo.get_full_tick() 接口返回的数据中,交易所直接提供了截止到当前时刻的累计成交额和累计成交量。因此,我们不需要自己去累加每一个 Tick 的数据,直接相除即可得到最精确的实时 VWAP。
代码实现
以下是一个完整的策略代码示例。该策略会在图表上实时计算并绘制 VWAP 曲线,同时在日志中打印数值。
# -*- coding: gbk -*-
import pandas as pd
def init(ContextInfo):
# 设置要操作的股票,这里以浦发银行为例
ContextInfo.stock = '600000.SH'
# 设置股票池
ContextInfo.set_universe([ContextInfo.stock])
# 设置运行周期,建议在分笔(Tick)或1分钟周期下运行以观察实时效果
ContextInfo.period = '1m'
def handlebar(ContextInfo):
# 获取当前主图或设置的股票代码
stock_code = ContextInfo.stock
# 1. 获取最新的 Tick 数据
# get_full_tick 返回的是一个字典,key是股票代码,value是数据字典
tick_data = ContextInfo.get_full_tick([stock_code])
# 检查是否成功获取数据
if not tick_data or stock_code not in tick_data:
return
current_tick = tick_data[stock_code]
# 2. 提取累计成交额和累计成交量
# amount: 成交额 (元)
# pvolume: 成交总量 (股) -> 注意:这里使用 pvolume (股数) 而不是 volume (手),以保证精度
total_amount = current_tick.get('amount', 0.0)
total_volume = current_tick.get('pvolume', 0.0)
# 3. 计算 VWAP
# 避免除以零(例如开盘前或刚开盘无成交时)
if total_volume > 0:
vwap = total_amount / total_volume
else:
# 如果没有成交量,可以用昨收盘价或开盘价暂代,或者设为0
vwap = current_tick.get('lastClose', 0.0)
# 4. 打印输出
# 获取当前时间戳并转换格式方便阅读
timetag = ContextInfo.get_tick_timetag()
time_str = timetag_to_datetime(timetag, '%Y-%m-%d %H:%M:%S')
print(f"时间: {time_str} | 代码: {stock_code} | 累计成交额: {total_amount:.2f} | 累计成交量(股): {total_volume} | 实时 VWAP: {vwap:.4f}")
# 5. 在图表上绘制 VWAP 曲线 (仅在回测或界面运行时有效)
# 第一个参数是指标名称,第二个是数值,第三个-1代表主图,第四个0代表曲线,颜色为黄色
ContextInfo.paint('VWAP', vwap, -1, 0, 'yellow')
# 可以在这里添加基于 VWAP 的交易逻辑
# 例如:如果最新价低于 VWAP 一定比例则买入
# last_price = current_tick.get('lastPrice')
# if last_price < vwap * 0.99:
# pass # 执行买入逻辑
代码详解
-
get_full_tick接口:- 这是获取实时快照数据的核心函数。
- 它返回的
amount是当日累计成交额(单位:元)。 - 它返回的
pvolume是当日累计成交量(单位:股)。注意不要混淆volume(通常单位是手),计算价格时必须用“元/股”。
-
除零保护:
- 在早盘集合竞价阶段或极不活跃的标的中,成交量可能为 0。代码中加入了
if total_volume > 0的判断,防止程序报错。
- 在早盘集合竞价阶段或极不活跃的标的中,成交量可能为 0。代码中加入了
-
paint函数:- 用于在 QMT 的 K 线图上画线。将 VWAP 画在主图上(参数
-1),可以直观地对比当前价格与 VWAP 的关系。
- 用于在 QMT 的 K 线图上画线。将 VWAP 画在主图上(参数
-
适用模式:
- 实盘/模拟交易:此逻辑完全适用,数据随行情实时推送更新。
- 回测模式:
get_full_tick在回测中通常只能获取到当前 Bar 的切片数据(如果回测精度不够高)。如果在回测中需要高精度的 VWAP,建议使用get_market_data_ex获取分钟线数据,通过cumsum()累加当天的成交额和成交量来模拟计算。
进阶:如何在回测中计算历史 VWAP?
如果你是在做历史回测(而不是实盘监控),且使用的是分钟线(如 1m, 5m),则不能依赖 get_full_tick(因为它只返回最新快照)。你需要利用 Pandas 对当天的分钟数据进行累加。
回测专用 VWAP 计算代码:
# -*- coding: gbk -*-
import pandas as pd
def init(ContextInfo):
ContextInfo.stock = '600000.SH'
ContextInfo.set_universe([ContextInfo.stock])
ContextInfo.period = '1m' # 必须是日内周期
def handlebar(ContextInfo):
# 获取当前 Bar 的时间
current_time = timetag_to_datetime(ContextInfo.get_bar_timetag(ContextInfo.barpos), '%Y%m%d')
# 获取当天的所有分钟数据(从当天开盘到现在)
# 注意:这里简化处理,实际回测中需要获取当天09:30到当前barpos的数据
# 在QMT回测引擎中,通常我们取过去一定数量的bar,然后按日期过滤
# 获取过去240根K线(覆盖一天4小时)
df = ContextInfo.get_market_data_ex(
['amount', 'volume'],
[ContextInfo.stock],
period=ContextInfo.period,
count=240,
dividend_type='none'
)[ContextInfo.stock]
# 转换索引为字符串日期格式以便筛选
df.index = df.index.astype(str)
# 筛选出属于“今天”的数据
# 假设 current_time 格式为 '20231027'
today_df = df[df.index.str.startswith(current_time)]
if not today_df.empty:
# 计算累计成交额和累计成交量
# 注意:分钟线里的 volume 通常是手,需要乘以 100 变成股
# 但 amount 是元。 VWAP = Sum(Amount) / (Sum(Volume_in_hands) * 100)
cum_amount = today_df['amount'].sum()
cum_volume = today_df['volume'].sum() * 100 # 转换为股
if cum_volume > 0:
vwap = cum_amount / cum_volume
ContextInfo.paint('VWAP_Backtest', vwap, -1, 0, 'cyan')
总结
- 实盘/实时监控:请使用第一种基于
get_full_tick的方法,这是最简单且计算量最小的方案,直接利用了交易所推送的累计数据。 - 历史回测:请使用第二种基于
get_market_data_ex并配合sum()或cumsum()的方法。
Q&A
Q: 为什么计算出来的 VWAP 和某些看盘软件上的有一点点误差?
A: 误差通常来源于“成交量单位”的处理。QMT 的 pvolume 是股数,volume 是手。如果软件A使用手计算而忽略了零股(不足一手的部分),或者对 amount 的精度处理不同,会导致微小差异。使用 amount / pvolume 是最标准的定义。
Q: 这个策略能用于期货吗?
A: 可以。但需要注意期货的 volume 单位通常就是“手”,且期货没有“股”的概念。对于期货,直接使用 amount / volume 即可,但需确认 amount 是否为合约价值(通常期货行情中的 amount 是成交金额,计算 VWAP 逻辑一致)。此外,期货还需要考虑合约乘数的问题,但在计算均价时,分子分母同比例变化,直接相除通常即可得到单价。
Q: get_full_tick 在回测模式下返回什么?
A: 在回测模式下,get_full_tick 返回的是当前回测时间点所在 K 线的收盘时的快照数据(如果是日线回测,就是当日收盘快照)。它无法在日线回测中模拟出盘中的动态变化。因此回测日内策略必须使用分钟周期。