🚀 AI 一键生成 qmt 策略代码

相对活力指数 (RVI) 的 Python 实现与 QMT 策略应用

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

问题描述

请提供一个相对活力指数 RVI 的交易策略源码

解决方案

这是一个基于 相对活力指数 (Relative Vigor Index, RVI) 的 QMT 量化交易策略源码。

策略原理

RVI 是一种振荡指标,用于衡量价格趋势的信念。其核心思想是:在上升趋势中,收盘价通常高于开盘价;在下降趋势中,收盘价通常低于开盘价。

交易逻辑:

  1. 计算 RVI 线:基于收盘价与开盘价的差值,对比最高价与最低价的差值,经过平滑处理得出。
  2. 计算 Signal 线:RVI 线的加权移动平均线。
  3. 买入信号:当 RVI 线 上穿 Signal 线(金叉)时,买入。
  4. 卖出信号:当 RVI 线 下穿 Signal 线(死叉)时,卖出(清仓)。

策略源码

# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 设置策略运行的标的,这里以浦发银行为例
    ContextInfo.stock = '600000.SH'
    
    # 设置策略运行周期,这里使用日线
    ContextInfo.period = '1d'
    
    # RVI 指标参数 N
    ContextInfo.N = 10
    
    # 设置资金账号 (请替换为您自己的资金账号)
    ContextInfo.account_id = '您的资金账号'
    ContextInfo.account_type = 'STOCK' # 股票账户
    
    # 设定股票池
    ContextInfo.set_universe([ContextInfo.stock])
    
    # 每次交易的金额(回测模式下使用)
    ContextInfo.trade_amount = 100000

def get_rvi(high, low, close, open_price, n):
    """
    计算 RVI 指标
    公式参考 Ehlers 的定义
    """
    # 1. 计算分子 (Close - Open) 的平滑值
    # Value1 = ((Close - Open) + 2 * (Close[1] - Open[1]) + 2 * (Close[2] - Open[2]) + (Close[3] - Open[3])) / 6
    co = close - open_price
    value1 = (co + 2 * co.shift(1) + 2 * co.shift(2) + co.shift(3)) / 6
    
    # 2. 计算分母 (High - Low) 的平滑值
    # Value2 = ((High - Low) + 2 * (High[1] - Low[1]) + 2 * (High[2] - Low[2]) + (High[3] - Low[3])) / 6
    hl = high - low
    value2 = (hl + 2 * hl.shift(1) + 2 * hl.shift(2) + hl.shift(3)) / 6
    
    # 3. 计算 N 周期的累加
    num = value1.rolling(window=n).sum()
    denom = value2.rolling(window=n).sum()
    
    # 4. 计算 RVI
    # 处理分母为0的情况
    rvi = np.where(denom != 0, num / denom, 0)
    rvi_series = pd.Series(rvi, index=close.index)
    
    # 5. 计算 Signal 线 (RVI 的加权移动平均)
    # Signal = (RVI + 2 * RVI[1] + 2 * RVI[2] + RVI[3]) / 6
    signal = (rvi_series + 2 * rvi_series.shift(1) + 2 * rvi_series.shift(2) + rvi_series.shift(3)) / 6
    
    return rvi_series, signal

def handlebar(ContextInfo):
    """
    K线逐根运行函数
    """
    # 获取当前 Bar 的索引
    index = ContextInfo.barpos
    
    # 获取当前时间
    realtime = ContextInfo.get_bar_timetag(index)
    
    # 获取标的代码
    stock = ContextInfo.stock
    
    # 获取历史行情数据
    # 为了计算 RVI 和 Signal,我们需要 N + 前置平滑(3) + 信号平滑(3) 的数据量,取 50 根比较稳妥
    count = ContextInfo.N + 20 
    
    # 使用 get_market_data_ex 获取数据 (推荐使用 ex 接口)
    # 注意:QMT 返回的数据是按列排列的 DataFrame
    data_map = ContextInfo.get_market_data_ex(
        ['open', 'high', 'low', 'close'], 
        [stock], 
        period=ContextInfo.period, 
        count=count, 
        dividend_type='front' # 前复权
    )
    
    if stock not in data_map:
        return
        
    df = data_map[stock]
    
    # 数据长度不足以计算指标时直接返回
    if len(df) < ContextInfo.N + 6:
        return

    # 计算 RVI 和 Signal
    rvi, signal = get_rvi(df['high'], df['low'], df['close'], df['open'], ContextInfo.N)
    
    # 获取最新和上一个周期的指标值
    # iloc[-1] 是当前根 K 线(如果是盘中,则是未走完的 K 线)
    # iloc[-2] 是上一根 K 线
    curr_rvi = rvi.iloc[-1]
    prev_rvi = rvi.iloc[-2]
    curr_signal = signal.iloc[-1]
    prev_signal = signal.iloc[-2]
    
    # 打印调试信息 (可选)
    # print(f"Time: {timetag_to_datetime(realtime, '%Y-%m-%d %H:%M:%S')} RVI: {curr_rvi:.4f}, Signal: {curr_signal:.4f}")
    
    # 获取当前持仓
    positions = ContextInfo.get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
    current_vol = 0
    for pos in positions:
        if pos.m_strInstrumentID == stock:
            current_vol = pos.m_nVolume
            break

    # --- 交易逻辑 ---
    
    # 1. 买入信号:RVI 上穿 Signal (金叉)
    if prev_rvi < prev_signal and curr_rvi > curr_signal:
        if current_vol == 0:
            print(f"产生买入信号: {stock}, RVI金叉")
            # 使用 order_target_value 调整持仓市值到指定金额
            # 注意:实盘中请确保 account_id 设置正确,并根据需要替换为 passorder
            order_target_value(stock, ContextInfo.trade_amount, ContextInfo, ContextInfo.account_id)

    # 2. 卖出信号:RVI 下穿 Signal (死叉)
    elif prev_rvi > prev_signal and curr_rvi < curr_signal:
        if current_vol > 0:
            print(f"产生卖出信号: {stock}, RVI死叉")
            # 清仓
            order_target_value(stock, 0, ContextInfo, ContextInfo.account_id)

    # 3. 绘图 (可选,用于回测界面展示)
    ContextInfo.paint('RVI', curr_rvi, -1, 0, 'red')
    ContextInfo.paint('Signal', curr_signal, -1, 0, 'green')

代码关键点说明

  1. 数据获取 (get_market_data_ex)

    • 使用了 get_market_data_ex 接口,这是 QMT 推荐的高效数据接口。
    • 设置了 dividend_type='front'(前复权),这对于技术指标计算非常重要,可以避免分红派息导致的价格跳空影响指标准确性。
  2. RVI 计算逻辑 (get_rvi)

    • 代码中严格遵循了 RVI 的经典算法(Ehlers 算法)。
    • 使用了 pandasshiftrolling 函数来处理平滑和移动求和,避免了复杂的循环,提高了计算效率。
    • 平滑处理:RVI 的核心在于分子(收盘-开盘)和分母(最高-最低)都经过了 [1, 2, 2, 1] 权重的平滑处理,这比简单的移动平均更能过滤噪声。
  3. 交易执行 (order_target_value)

    • 使用了 order_target_value 函数。这个函数非常适合策略编写,因为它会自动计算需要买入或卖出的数量。
    • 买入时:将持仓调整到 ContextInfo.trade_amount(例如 10 万元)。
    • 卖出时:将持仓调整到 0(清仓)。
  4. 绘图 (paint)

    • 在回测界面的副图中绘制了 RVI(红线)和 Signal(绿线),方便直观验证策略信号。

如何使用

  1. 打开 QMT 客户端,进入“模型研究”或“策略编辑器”。
  2. 新建一个 Python 策略。
  3. 将上述代码复制粘贴进去。
  4. 修改账号:将 ContextInfo.account_id = '您的资金账号' 修改为您实际的资金账号。
  5. 点击“编译”检查无误后,可以进行“回测”或在实盘/模拟盘中“运行”。

Q&A

Q: 为什么 RVI 指标计算要用 shift(1), shift(2) 等?
A: RVI 的经典公式中包含了一个类似于 FIR 滤波器的平滑过程(权重为 1/6, 2/6, 2/6, 1/6),这需要用到当前周期及前三个周期的数据。shift(n) 用于获取前 n 个周期的数据。

Q: 这个策略适合什么周期?
A: RVI 是一种短线振荡指标,通常在日线或 60 分钟线上效果较好。如果用于分钟级(如 1 分钟、5 分钟),噪音可能会比较大,建议适当增大参数 N。

Q: 如何在实盘中避免信号闪烁?
A: 当前代码使用的是 curr_rvi (当前 K 线) 和 prev_rvi (上一根 K 线)。在实盘中,当前 K 线未走完时,curr_rvi 会随价格跳动。如果希望信号完全确认后再下单,可以在 handlebar 开头添加 if not ContextInfo.is_last_bar(): return,但这会导致回测无法进行。更通用的做法是只在 K 线收盘时(下一根 K 线开始瞬间)或者使用 ContextInfo.is_new_bar() 逻辑来触发下单。上述代码为了回测演示方便,采用了实时计算逻辑。