问题描述
解决方案
这是一个基于 QMT 平台的 VWAP(成交量加权平均价) 交易策略。
策略逻辑
- VWAP 计算:VWAP 通常是一个日内指标。公式为:$\text{VWAP} = \frac{\text{当日累计成交额}}{\text{当日累计成交量}}$。
- 交易信号:
- 做多:当当前收盘价 大于 当日 VWAP,且当前无持仓时,买入。
- 做空/平仓:当当前收盘价 小于 当日 VWAP,且当前有持仓时,卖出(平仓)。
- 运行周期:建议在 分钟线(如 '1m', '3m', '5m')上运行,以便在日内捕捉价格相对于均价的波动。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,策略启动时调用一次
"""
# 设置要交易的股票代码,这里以浦发银行为例
ContextInfo.stock_code = '600000.SH'
# 设置股票池
ContextInfo.set_universe([ContextInfo.stock_code])
# 设置交易账号(请替换为您真实的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 定义一个全局变量记录持仓状态 (1: 持仓, 0: 空仓)
# 注意:实盘中建议使用 get_trade_detail_data 获取真实持仓
ContextInfo.holding = 0
# 每次下单的数量(股)
ContextInfo.trade_vol = 1000
def handlebar(ContextInfo):
"""
行情事件函数,每根 K 线运行一次
"""
# 获取当前 K 线的位置
index = ContextInfo.barpos
# 获取当前时间戳
timetag = ContextInfo.get_bar_timetag(index)
# 将时间戳转换为日期字符串 (格式 YYYYMMDD),用于判断是否为同一天
# format参数根据需求设定,这里只需要日期部分
current_date_str = timetag_to_datetime(timetag, '%Y%m%d')
# 获取行情数据
# 为了计算日内 VWAP,我们需要获取当天开盘以来的数据
# 假设是分钟线,一天最多240根K线,取最近240根足以覆盖当天
data_map = ContextInfo.get_market_data_ex(
['close', 'amount', 'volume'],
[ContextInfo.stock_code],
period=ContextInfo.period,
count=240,
dividend_type='follow'
)
if ContextInfo.stock_code not in data_map:
return
df = data_map[ContextInfo.stock_code]
# 确保数据不为空
if df.empty:
return
# --- 核心逻辑:计算日内 VWAP ---
# 1. 将索引(时间字符串)转换为日期格式,以便筛选当天数据
# 注意:get_market_data_ex 返回的 DataFrame index 是字符串格式的时间
df['datetime_str'] = df.index
# 提取日期部分 (假设索引格式为 YYYYMMDDHHMMSS)
df['date'] = df['datetime_str'].apply(lambda x: x[0:8])
# 2. 筛选出属于“今天”的数据
today_df = df[df['date'] == current_date_str]
if today_df.empty:
return
# 3. 计算累计成交额和累计成交量
cumulative_amount = today_df['amount'].sum()
cumulative_volume = today_df['volume'].sum()
# 4. 计算 VWAP
if cumulative_volume == 0:
return
vwap = cumulative_amount / cumulative_volume
# 获取当前 K 线的收盘价
current_close = today_df['close'].iloc[-1]
# --- 交易逻辑 ---
# 打印调试信息 (可在日志中查看)
print(f"时间: {timetag_to_datetime(timetag, '%Y-%m-%d %H:%M:%S')}, 收盘价: {current_close:.2f}, VWAP: {vwap:.2f}")
# 信号 1: 价格上穿 VWAP -> 买入
if current_close > vwap and ContextInfo.holding == 0:
print("信号触发: 价格高于 VWAP,买入")
# 下单买入
order_shares(ContextInfo.stock_code, ContextInfo.trade_vol, 'fix', current_close, ContextInfo, ContextInfo.account_id)
ContextInfo.holding = 1
# 信号 2: 价格下穿 VWAP -> 卖出 (平仓)
elif current_close < vwap and ContextInfo.holding > 0:
print("信号触发: 价格低于 VWAP,卖出")
# 下单卖出 (负数代表卖出)
order_shares(ContextInfo.stock_code, -ContextInfo.trade_vol, 'fix', current_close, ContextInfo, ContextInfo.account_id)
ContextInfo.holding = 0
代码关键点说明
-
数据获取 (
get_market_data_ex):- 我们获取了
amount(成交额) 和volume(成交量)。 count=240:因为 A 股一天大约有 240 分钟的交易时间,获取最近 240 根分钟 K 线通常能覆盖当天的所有数据。
- 我们获取了
-
日内数据筛选:
- VWAP 是一个日内指标,每天开盘时需要重置。
- 代码通过
timetag_to_datetime获取当前 K 线的日期,并在 DataFrame 中筛选出date等于当前日期的所有行,从而实现仅基于当天数据计算 VWAP。
-
VWAP 公式:
- 代码中使用了标准的计算方式:
Sum(当日所有K线成交额) / Sum(当日所有K线成交量)。
- 代码中使用了标准的计算方式:
-
交易执行 (
order_shares):- 使用
order_shares进行指定股数下单。 - 正数表示买入,负数表示卖出。
'fix'表示限价单(这里用的是当前收盘价作为委托价,回测中通常能成交,实盘中可能需要用对手价'COMPETE'或市价以保证成交)。
- 使用
使用建议
- 周期设置:在 QMT 界面运行或回测时,请将主图周期设置为 1分钟 或 5分钟。如果设置为日线,VWAP 将等于当天的均价,策略意义不大。
- 实盘账号:请务必在
init函数中将YOUR_ACCOUNT_ID替换为您真实的资金账号。 - 做空说明:上述代码在“低于 VWAP”时执行的是卖出平仓操作(适用于 A 股普通账户)。如果您是期货或融券账户想要进行真正的“开空单”,需要修改下单逻辑(例如使用
passorder函数并指定开空操作码)。
Q&A
Q: 如何将此策略改为期货策略(支持开空单)?
A: 需要将 order_shares 替换为 buy_open (买入开仓), sell_close (卖出平仓), sell_open (卖出开仓), buy_close (买入平仓) 等期货专用函数,或者使用 passorder 函数配合期货的操作码(如 0:开多, 3:开空)。
Q: 为什么 VWAP 计算要筛选当天的日期?
A: VWAP 的定义是“成交量加权平均价”,通常指当日的均价。如果不重置,计算的就是过去 N 天的移动 VWAP,这与通用的 VWAP 交易逻辑不同。
Q: ContextInfo.holding 变量在实盘中可靠吗?
A: 不完全可靠。在实盘中,程序重启后变量会重置。建议在实盘中使用 get_trade_detail_data(ContextInfo.account_id, 'stock', 'position') 来查询真实的账户持仓情况。