问题描述
解决方案
这是一个关于如何在 QMT (Quantitative Trading Platform) 平台编写 VR (Volume Ratio,成交量比率) 策略的详细指南。
1. 策略原理
VR 指标(成交量比率)主要用于分析股价与成交量的关系,通过比较上涨日的成交量与下跌日的成交量,来判断市场的买卖气势。
VR 计算公式(N日):
$$VR = \frac{AVS + \frac{1}{2}CVS}{BVS + \frac{1}{2}CVS} \times 100$$
其中:
- $AVS$:N日内股价上涨日的成交量总和。
- $BVS$:N日内股价下跌日的成交量总和。
- $CVS$:N日内股价平盘日的成交量总和。
- $N$:通常取 26 日。
交易逻辑(示例):
- 低位买入:当 VR 值低于 70(通常视为超卖区域),认为市场可能筑底,执行买入。
- 高位卖出:当 VR 值高于 250(通常视为超买区域),认为市场过热,执行卖出。
- 仓位控制:全仓买入或全仓卖出。
2. 策略代码实现
以下是完整的 Python 策略代码。代码包含了数据获取、VR 指标计算以及交易信号的执行。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数和账户
"""
# 1. 设置策略参数
ContextInfo.N = 26 # VR指标周期,默认26
ContextInfo.buy_threshold = 70 # 买入阈值(超卖)
ContextInfo.sell_threshold = 250 # 卖出阈值(超买)
# 2. 设置股票池 (示例:平安银行)
# 在实际回测或实盘中,可以通过界面设置,也可以代码指定
ContextInfo.stock_code = '000001.SZ'
ContextInfo.set_universe([ContextInfo.stock_code])
# 3. 设置资金账号 (实盘/模拟盘需要修改此处账号)
# ContextInfo.set_account('您的资金账号')
# 4. 设置回测相关 (仅回测有效)
ContextInfo.set_slippage(1, 0.0) # 设置滑点
ContextInfo.set_commission(0, [0.0003, 0.0013, 0.0003, 0.0003, 0.0003, 5]) # 设置费率
def handlebar(ContextInfo):
"""
K线周期运行函数,每个周期执行一次
"""
# 获取当前K线位置
index = ContextInfo.barpos
# 获取当前时间戳并转换为字符串格式,用于获取截止当前的数据
real_timetag = ContextInfo.get_bar_timetag(index)
current_date_str = timetag_to_datetime(real_timetag, '%Y%m%d%H%M%S')
# 获取历史行情数据
# 我们需要 N+1 个数据来计算 N 个周期的涨跌变化(因为需要对比前一日收盘价)
count = ContextInfo.N + 1
# 使用 get_market_data_ex 获取数据 (推荐使用 ex 接口)
# 字段:close (收盘价), volume (成交量)
market_data = ContextInfo.get_market_data_ex(
['close', 'volume'],
[ContextInfo.stock_code],
period=ContextInfo.period,
end_time=current_date_str,
count=count,
dividend_type='front' # 前复权
)
# 提取该股票的 DataFrame
if ContextInfo.stock_code not in market_data:
return
df = market_data[ContextInfo.stock_code]
# 数据长度不足 N+1 时无法计算,直接返回
if len(df) < count:
return
# --- VR 指标计算逻辑 ---
# 计算每日的价格变化:今日收盘 - 昨日收盘
# diff > 0: 上涨, diff < 0: 下跌, diff = 0: 平盘
df['diff'] = df['close'].diff()
# 去掉第一行(因为 diff 是 NaN)
df_calc = df.iloc[1:]
# 分类汇总成交量
# AVS: 上涨日成交量
avs = df_calc.loc[df_calc['diff'] > 0, 'volume'].sum()
# BVS: 下跌日成交量
bvs = df_calc.loc[df_calc['diff'] < 0, 'volume'].sum()
# CVS: 平盘日成交量
cvs = df_calc.loc[df_calc['diff'] == 0, 'volume'].sum()
# 计算 VR
# 公式: (AVS + 1/2 * CVS) / (BVS + 1/2 * CVS) * 100
denominator = bvs + 0.5 * cvs
if denominator == 0:
# 分母为0的极端情况(极少见,通常设为最大值或忽略)
vr_value = 1000
else:
vr_value = (avs + 0.5 * cvs) / denominator * 100
# 打印 VR 值用于调试 (可在日志中查看)
# print(f"Time: {current_date_str}, VR: {vr_value:.2f}")
# --- 交易逻辑 ---
# 获取当前持仓
positions = ContextInfo.get_trade_detail_data(ContextInfo.accid, 'stock', 'position')
current_holding = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == ContextInfo.stock_code:
current_holding = pos.m_nVolume
break
# 1. 买入信号:VR < 买入阈值 (超卖,看多)
if vr_value < ContextInfo.buy_threshold:
# 如果当前没有持仓,则全仓买入
# 使用 order_target_percent 调整仓位到 100% (1.0)
if current_holding == 0:
print(f"买入信号触发 - 时间: {current_date_str}, VR: {vr_value:.2f}")
order_target_percent(ContextInfo.stock_code, 1.0, ContextInfo, ContextInfo.accid)
# 2. 卖出信号:VR > 卖出阈值 (超买,看空)
elif vr_value > ContextInfo.sell_threshold:
# 如果当前有持仓,则清仓
# 使用 order_target_percent 调整仓位到 0% (0.0)
if current_holding > 0:
print(f"卖出信号触发 - 时间: {current_date_str}, VR: {vr_value:.2f}")
order_target_percent(ContextInfo.stock_code, 0.0, ContextInfo, ContextInfo.accid)
3. 代码详细解析
-
编码声明 (
# -*- coding: gbk -*-):- QMT 的 Python 编辑器默认使用 GBK 编码,必须在首行添加此声明,否则中文注释会导致乱码或报错。
-
init初始化函数:- 定义了 VR 指标的核心参数
N(周期)、buy_threshold(买入阈值)和sell_threshold(卖出阈值)。 - 使用
set_universe设定股票池。 - 注意:在实盘运行时,请取消
set_account的注释并填入真实的资金账号。
- 定义了 VR 指标的核心参数
-
数据获取 (
get_market_data_ex):- 我们使用
get_market_data_ex接口,这是 QMT 推荐的高效数据接口。 count = ContextInfo.N + 1:因为计算涨跌幅需要对比前一日收盘价,所以获取 26 天的 VR 指标需要 27 根 K 线的数据。dividend_type='front':使用前复权数据,保证价格连续性,避免分红除权干扰指标计算。
- 我们使用
-
VR 计算逻辑:
- 利用 Pandas 的
diff()函数快速计算当日收盘价与前一日收盘价的差值。 - 利用 Pandas 的布尔索引 (
df_calc['diff'] > 0) 快速筛选出上涨、下跌和平盘的日期,并对对应的volume列求和。 - 严格按照 VR 公式进行计算,并处理了分母为 0 的极端情况。
- 利用 Pandas 的
-
交易执行:
- 使用
order_target_percent函数。这是一个非常方便的高级交易函数:- 传入
1.0代表买入至满仓。 - 传入
0.0代表卖出至空仓。
- 传入
- 它会自动计算需要买卖的数量,无需手动计算手数。
- 使用
4. 如何在 QMT 中运行
- 打开 QMT 客户端,进入 “模型研究” -> “策略编辑器”。
- 新建一个 Python 策略。
- 将上述代码完整复制粘贴到编辑器中。
- 在右侧参数面板或代码中确认
ContextInfo.stock_code是你想回测的股票。 - 点击 “编译” 检查是否有语法错误。
- 点击 “回测”,选择回测的时间段(例如最近一年),点击开始即可查看策略表现。
Q&A: 常见问题解答
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 较新的接口,返回的数据结构更清晰(字典包含 DataFrame),且在处理多股、多周期数据时性能更好,参数定义也更规范。
Q: VR 指标的参数 N 只能是 26 吗?
A: 不一定。26 是经典参数,但在量化交易中,您可以根据回测结果调整这个参数(例如改为 12 或 30)以适应不同的个股特性。
Q: 如何将此策略改为多只股票轮动?
A: 您需要在 init 中将 stock_code 设置为一个列表(如 ['000001.SZ', '600000.SH']),然后在 handlebar 中使用 for 循环遍历这个列表,对每一只股票分别计算 VR 值并独立发出交易指令。
Q: 为什么代码中要处理分母为 0 的情况?
A: 虽然在 26 个交易日内所有日子都上涨(导致下跌成交量 BVS 为 0)的情况非常罕见,但在编程中必须考虑这种边界情况,否则一旦发生会导致程序崩溃停止运行。