3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于 相对活力指数 (Relative Vigor Index, RVI) 的 QMT 量化交易策略源码。
RVI 是一种振荡指标,用于衡量价格趋势的信念。其核心思想是:在上升趋势中,收盘价通常高于开盘价;在下降趋势中,收盘价通常低于开盘价。
交易逻辑:
# -*- 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')
数据获取 (get_market_data_ex):
get_market_data_ex 接口,这是 QMT 推荐的高效数据接口。dividend_type='front'(前复权),这对于技术指标计算非常重要,可以避免分红派息导致的价格跳空影响指标准确性。RVI 计算逻辑 (get_rvi):
pandas 的 shift 和 rolling 函数来处理平滑和移动求和,避免了复杂的循环,提高了计算效率。[1, 2, 2, 1] 权重的平滑处理,这比简单的移动平均更能过滤噪声。交易执行 (order_target_value):
order_target_value 函数。这个函数非常适合策略编写,因为它会自动计算需要买入或卖出的数量。ContextInfo.trade_amount(例如 10 万元)。绘图 (paint):
ContextInfo.account_id = '您的资金账号' 修改为您实际的资金账号。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() 逻辑来触发下单。上述代码为了回测演示方便,采用了实时计算逻辑。