🚀 AI 一键生成 qmt 策略代码

QMT 教程:VR (Volume Ratio) 成交量比率策略开发

qmt | 发布时间: 2025/12/22
以下内容由 EasyQuant 生成。

问题描述

怎么编写 VR 成交量比率策略?

解决方案

这是一个关于如何在 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 日。

交易逻辑(示例):

  1. 低位买入:当 VR 值低于 70(通常视为超卖区域),认为市场可能筑底,执行买入。
  2. 高位卖出:当 VR 值高于 250(通常视为超买区域),认为市场过热,执行卖出。
  3. 仓位控制:全仓买入或全仓卖出。

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. 代码详细解析

  1. 编码声明 (# -*- coding: gbk -*-)

    • QMT 的 Python 编辑器默认使用 GBK 编码,必须在首行添加此声明,否则中文注释会导致乱码或报错。
  2. init 初始化函数

    • 定义了 VR 指标的核心参数 N(周期)、buy_threshold(买入阈值)和 sell_threshold(卖出阈值)。
    • 使用 set_universe 设定股票池。
    • 注意:在实盘运行时,请取消 set_account 的注释并填入真实的资金账号。
  3. 数据获取 (get_market_data_ex)

    • 我们使用 get_market_data_ex 接口,这是 QMT 推荐的高效数据接口。
    • count = ContextInfo.N + 1:因为计算涨跌幅需要对比前一日收盘价,所以获取 26 天的 VR 指标需要 27 根 K 线的数据。
    • dividend_type='front':使用前复权数据,保证价格连续性,避免分红除权干扰指标计算。
  4. VR 计算逻辑

    • 利用 Pandas 的 diff() 函数快速计算当日收盘价与前一日收盘价的差值。
    • 利用 Pandas 的布尔索引 (df_calc['diff'] > 0) 快速筛选出上涨、下跌和平盘的日期,并对对应的 volume 列求和。
    • 严格按照 VR 公式进行计算,并处理了分母为 0 的极端情况。
  5. 交易执行

    • 使用 order_target_percent 函数。这是一个非常方便的高级交易函数:
      • 传入 1.0 代表买入至满仓。
      • 传入 0.0 代表卖出至空仓。
    • 它会自动计算需要买卖的数量,无需手动计算手数。

4. 如何在 QMT 中运行

  1. 打开 QMT 客户端,进入 “模型研究” -> “策略编辑器”
  2. 新建一个 Python 策略
  3. 将上述代码完整复制粘贴到编辑器中。
  4. 在右侧参数面板或代码中确认 ContextInfo.stock_code 是你想回测的股票。
  5. 点击 “编译” 检查是否有语法错误。
  6. 点击 “回测”,选择回测的时间段(例如最近一年),点击开始即可查看策略表现。

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)的情况非常罕见,但在编程中必须考虑这种边界情况,否则一旦发生会导致程序崩溃停止运行。