🚀 AI 一键生成 qmt 策略代码

带中间带的 CR 能量指标策略在 QMT 中的 Python 实现

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

问题描述

如何用 CR 指标写一个买卖策略?

解决方案

这是一个基于 QMT 平台 Python API 编写的 CR 指标(能量指标) 交易策略。

策略逻辑说明

CR 指标(Energy of Market)用于判断买卖气势的强弱。本策略采用经典的 CR 与其均线交叉 的逻辑:

  1. CR 计算公式
    • 中间价 (MID) = (昨日最高价 + 昨日最低价) / 2
    • 上升值 = 今日最高价 - MID(若为负则记为0)
    • 下跌值 = MID - 今日最低价(若为负则记为0)
    • CR(N) = (N日内上升值之和 / N日内下跌值之和) * 100
  2. 交易信号
    • 买入信号(金叉):当 CR 线上穿 CR 均线(MA_CR),且 CR 值处于较低位置(例如小于 150,防止高位追涨),全仓买入。
    • 卖出信号(死叉):当 CR 线其下穿 CR 均线(MA_CR),或者 CR 进入极度超买区(例如大于 300),清仓卖出。

策略代码

# -*- 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.account_type = 'STOCK' # 股票账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 策略参数设置
    ContextInfo.N = 26       # CR指标的计算周期
    ContextInfo.M = 10       # CR均线的计算周期
    ContextInfo.period = '1d' # 运行周期:日线
    
    # 买卖阈值设置
    ContextInfo.buy_threshold = 150  # 买入时CR不应过高
    ContextInfo.sell_threshold = 300 # 止盈/超买阈值

def get_cr_indicator(ContextInfo, stock_code):
    """
    计算CR指标及其均线
    """
    # 获取足够的历史数据,长度需要覆盖 N + M + 缓冲
    count = ContextInfo.N + ContextInfo.M + 20
    
    # 使用 get_market_data_ex 获取数据
    # 注意:CR计算需要 High, Low
    data_dict = ContextInfo.get_market_data_ex(
        ['high', 'low', 'close'], 
        [stock_code], 
        period=ContextInfo.period, 
        count=count, 
        dividend_type='front' # 前复权
    )
    
    if stock_code not in data_dict:
        return None, None
        
    df = data_dict[stock_code]
    
    if len(df) < ContextInfo.N + 1:
        return None, None

    # 1. 计算中间价 MID = (昨日最高 + 昨日最低) / 2
    # shift(1) 表示取前一行的数据
    df['ref_high'] = df['high'].shift(1)
    df['ref_low'] = df['low'].shift(1)
    df['mid_price'] = (df['ref_high'] + df['ref_low']) / 2
    
    # 2. 计算上升值和下跌值
    # 上升值 = max(0, 今日最高 - 昨日中间价)
    df['up_strength'] = (df['high'] - df['mid_price']).clip(lower=0)
    # 下跌值 = max(0, 昨日中间价 - 今日最低)
    df['down_strength'] = (df['mid_price'] - df['low']).clip(lower=0)
    
    # 3. 计算 CR = N日上升和 / N日下跌和 * 100
    # rolling(N).sum() 计算滚动求和
    df['p1_sum'] = df['up_strength'].rolling(window=ContextInfo.N).sum()
    df['p2_sum'] = df['down_strength'].rolling(window=ContextInfo.N).sum()
    
    # 处理分母为0的情况,避免报错
    df['cr'] = np.where(df['p2_sum'] == 0, 0, (df['p1_sum'] / df['p2_sum']) * 100)
    
    # 4. 计算 CR 的均线
    df['cr_ma'] = df['cr'].rolling(window=ContextInfo.M).mean()
    
    # 返回最后两行数据用于判断交叉
    return df.iloc[-2], df.iloc[-1]

def handlebar(ContextInfo):
    """
    K线周期回调函数
    """
    # 获取当前正在处理的K线索引
    index = ContextInfo.barpos
    
    # 获取当前时间
    realtime = ContextInfo.get_bar_timetag(index)
    
    stock = ContextInfo.stock_code
    
    # 计算指标
    prev_bar, curr_bar = get_cr_indicator(ContextInfo, stock)
    
    if prev_bar is None or curr_bar is None:
        return # 数据不足,跳过
        
    # 提取当前和上一周期的 CR 及 MA 值
    cr_curr = curr_bar['cr']
    cr_prev = prev_bar['cr']
    ma_curr = curr_bar['cr_ma']
    ma_prev = prev_bar['cr_ma']
    
    # 检查是否包含无效值(NaN)
    if np.isnan(cr_curr) or np.isnan(ma_curr) or np.isnan(cr_prev):
        return

    # 获取当前持仓
    positions = ContextInfo.get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
    holding_vol = 0
    for pos in positions:
        if pos.m_strInstrumentID == stock:
            holding_vol = pos.m_nVolume
            break

    # --- 交易逻辑 ---
    
    # 1. 买入条件:
    # (1) CR 上穿 MA (金叉)
    # (2) CR 当前值小于设定的安全阈值 (避免高位接盘)
    is_golden_cross = (cr_prev <= ma_prev) and (cr_curr > ma_curr)
    is_safe_zone = cr_curr < ContextInfo.buy_threshold
    
    if is_golden_cross and is_safe_zone:
        if holding_vol == 0:
            # 全仓买入 (目标仓位调整为 100%)
            print(f"[{ContextInfo.barpos}] 金叉买入信号: CR={cr_curr:.2f}, MA={ma_curr:.2f}")
            order_target_percent(stock, 1.0, ContextInfo, ContextInfo.account_id)

    # 2. 卖出条件:
    # (1) CR 下穿 MA (死叉)
    # (2) 或者 CR 超过超买阈值 (止盈风控)
    is_death_cross = (cr_prev >= ma_prev) and (cr_curr < ma_curr)
    is_overbought = cr_curr > ContextInfo.sell_threshold
    
    if (is_death_cross or is_overbought) and holding_vol > 0:
        # 清仓卖出 (目标仓位调整为 0%)
        reason = "死叉" if is_death_cross else "超买"
        print(f"[{ContextInfo.barpos}] {reason}卖出信号: CR={cr_curr:.2f}, MA={ma_curr:.2f}")
        order_target_percent(stock, 0.0, ContextInfo, ContextInfo.account_id)

    # 可视化输出 (仅在回测模式下有效,画在副图)
    ContextInfo.paint('CR', cr_curr, -1, 0, 'red')
    ContextInfo.paint('CR_MA', ma_curr, -1, 0, 'yellow')

代码关键点解析

  1. 数据获取 (get_market_data_ex)

    • 我们使用了 get_market_data_ex 接口,这是 QMT 推荐的高效数据接口。
    • 获取了 high, low, close 字段。虽然 CR 计算主要用 High 和 Low,但 Close 通常用于复权计算和后续可能的扩展。
    • count 设置为 N + M + 20,确保有足够的历史数据来计算移动平均线,避免计算初期出现 NaN(空值)。
  2. CR 指标计算 (get_cr_indicator)

    • 利用 pandas 的矢量化运算,避免了低效的循环。
    • shift(1) 用于获取昨日数据。
    • clip(lower=0) 替代了 max(x, 0) 的逻辑,处理负值情况。
    • rolling(window=N).sum() 用于计算 N 日内的累加和。
  3. 交易执行 (order_target_percent)

    • 使用了 order_target_percent 函数。这是一个高级交易函数,它会自动计算需要买入或卖出的数量,将该股票的持仓调整到账户总资产的指定比例(这里是 1.0 即 100% 买入,0.0 即清仓)。
    • 相比 passorder,这个函数在编写策略逻辑时更简洁,不需要手动计算手数。
  4. 绘图 (paint)

    • 在代码末尾加入了 ContextInfo.paint,这样在回测界面的副图中可以看到 CR 和 CR_MA 的走势,方便验证策略信号是否正确。

如何使用

  1. 打开 QMT 客户端,进入“模型研究”或“策略编辑器”。
  2. 新建一个 Python 策略。
  3. 将上述代码完整复制进去。
  4. 修改 init 函数中的 ContextInfo.account_id 为您的实际资金账号(如果是回测,系统会自动分配虚拟账号,可不改)。
  5. 点击“编译”检查无误后,点击“回测”即可看到策略在历史行情下的表现。

Q&A

Q: 为什么 CR 指标要用昨日的中间价而不是今日的开盘价?
A: CR 指标的设计初衷是认为昨日的中间价((最高+最低)/2)是一个比较客观的均衡点。用今日最高/最低价与昨日均衡点比较,更能反映出今日多空双方相对于昨日均衡点的强弱程度。

Q: 代码中的 ContextInfo.buy_threshold = 150 是什么意思?
A: 这是一个风控条件。CR 指标如果数值过高(例如超过 200 或 300),说明市场已经过热,此时即使出现金叉也可能是诱多。限制买入阈值可以过滤掉高位风险。

Q: 如何将此策略应用到多只股票?
A: 在 init 函数中,将 ContextInfo.stock_code 改为一个列表,例如 ['600000.SH', '000001.SZ']。然后将 handlebar 函数中的逻辑放入一个 for stock in ContextInfo.get_universe(): 的循环中即可。