🚀 AI 一键生成 qmt 策略代码

如何基于 ATR 指标在 QMT 平台构建一个通道突破策略?

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

问题描述

请帮我写一个基于 ATR 指标的通道突破策略。

解决方案

这是一个基于 ATR(平均真实波幅)指标的通道突破策略。

策略逻辑

  1. 计算 ATR:衡量市场的波动率。
  2. 构建通道
    • 中轨:过去 N 天的收盘价移动平均线 (MA)。
    • 上轨:中轨 + (ATR * 乘数)。
    • 下轨:中轨 - (ATR * 乘数)。
  3. 交易信号
    • 买入开仓:当价格突破上轨时,认为趋势向上,买入。
    • 卖出平仓:当价格跌破下轨(或中轨,视激进程度而定,本策略采用跌破下轨反转)时,卖出。

策略代码

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

def init(ContextInfo):
    """
    策略初始化函数
    """
    # --- 策略参数设置 ---
    ContextInfo.stock_code = '600000.SH'  # 标的股票:浦发银行
    ContextInfo.period = 20               # ATR 和 均线的计算周期
    ContextInfo.multiplier = 2.0          # 通道宽度乘数 (上轨 = MA + multiplier * ATR)
    ContextInfo.account_id = 'YOUR_ACCOUNT_ID' # 请替换为您的资金账号
    ContextInfo.trade_qty = 100           # 每次交易数量
    
    # --- 设置股票池 ---
    ContextInfo.set_universe([ContextInfo.stock_code])
    
    # --- 设置交易账号 ---
    ContextInfo.set_account(ContextInfo.account_id)

def get_position(ContextInfo, stock_code):
    """
    获取当前持仓数量的辅助函数
    """
    try:
        # 获取持仓对象列表
        positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
        for pos in positions:
            if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock_code:
                return pos.m_nVolume
        return 0
    except Exception as e:
        print("获取持仓失败: ", e)
        return 0

def calculate_atr_bands(df, period, multiplier):
    """
    计算 ATR 和 通道上下轨
    """
    # 1. 计算 TR (True Range)
    # TR = Max(High-Low, Abs(High-PreClose), Abs(Low-PreClose))
    df['high_low'] = df['high'] - df['low']
    df['pre_close'] = df['close'].shift(1)
    df['high_pre_close'] = abs(df['high'] - df['pre_close'])
    df['low_pre_close'] = abs(df['low'] - df['pre_close'])
    
    df['tr'] = df[['high_low', 'high_pre_close', 'low_pre_close']].max(axis=1)
    
    # 2. 计算 ATR (简单移动平均)
    df['atr'] = df['tr'].rolling(window=period).mean()
    
    # 3. 计算中轨 (MA)
    df['mid'] = df['close'].rolling(window=period).mean()
    
    # 4. 计算上下轨
    df['upper'] = df['mid'] + (multiplier * df['atr'])
    df['lower'] = df['mid'] - (multiplier * df['atr'])
    
    return df

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 获取当前 Bar 的索引
    index = ContextInfo.barpos
    stock_code = ContextInfo.stock_code
    
    # 获取历史行情数据 (取足够多的数据以计算指标,例如 period + 10)
    # 注意:get_market_data_ex 返回的是字典 {code: dataframe}
    data_count = ContextInfo.period + 10
    market_data = ContextInfo.get_market_data_ex(
        ['open', 'high', 'low', 'close'], 
        [stock_code], 
        period='1d', 
        count=data_count,
        dividend_type='front' # 前复权
    )
    
    if stock_code not in market_data:
        return
        
    df = market_data[stock_code]
    
    # 数据量不足时不计算
    if len(df) < ContextInfo.period + 1:
        return

    # 计算指标
    df = calculate_atr_bands(df, ContextInfo.period, ContextInfo.multiplier)
    
    # 获取最新完成的一根K线的数据(用于生成信号)
    # 注意:实盘中通常使用上一根K线的收盘价来决定是否在当前时刻交易,避免未来函数
    last_bar = df.iloc[-2]  # 上一根K线(已走完)
    curr_bar = df.iloc[-1]  # 当前K线(正在进行)
    
    # 获取当前持仓
    current_pos = get_position(ContextInfo, stock_code)
    
    # --- 交易逻辑 ---
    
    # 1. 买入信号:上一根K线收盘价突破上轨
    if last_bar['close'] > last_bar['upper']:
        if current_pos == 0:
            print(f"时间: {curr_bar.name}, 价格突破上轨,买入开仓。收盘价: {last_bar['close']}, 上轨: {last_bar['upper']}")
            # 23: 买入, 1101: 单股单账号普通下单, 5: 最新价
            passorder(23, 1101, ContextInfo.account_id, stock_code, 5, -1, ContextInfo.trade_qty, ContextInfo)
            
    # 2. 卖出信号:上一根K线收盘价跌破下轨
    elif last_bar['close'] < last_bar['lower']:
        if current_pos > 0:
            print(f"时间: {curr_bar.name}, 价格跌破下轨,卖出平仓。收盘价: {last_bar['close']}, 下轨: {last_bar['lower']}")
            # 24: 卖出
            passorder(24, 1101, ContextInfo.account_id, stock_code, 5, -1, current_pos, ContextInfo)

    # --- (可选) 绘图 ---
    # 在回测界面绘制指标
    ContextInfo.paint('Upper', last_bar['upper'], -1, 0, 'red')
    ContextInfo.paint('Lower', last_bar['lower'], -1, 0, 'green')
    ContextInfo.paint('Mid', last_bar['mid'], -1, 0, 'white')

代码关键点解析

  1. # -*- coding: gbk -*-:

    • QMT 的 Python 编辑器默认使用 GBK 编码,这行代码对于防止中文注释乱码至关重要。
  2. get_market_data_ex:

    • 这是 QMT 获取行情的推荐接口。它返回的是一个 Pandas DataFrame,非常适合进行向量化的指标计算。
    • 我们获取了 period + 10 条数据,确保有足够的数据来计算移动平均和 ATR。
  3. ATR 计算逻辑:

    • 首先计算 TR(真实波幅),它是当日最高价与最低价之差、当日最高价与昨日收盘价之差的绝对值、当日最低价与昨日收盘价之差的绝对值中的最大值。
    • 然后对 TR 进行移动平均得到 ATR。
  4. 信号生成 (iloc[-2]):

    • handlebar 中,df.iloc[-1] 通常代表当前正在跳动的 K 线(未走完)。
    • 为了避免“未来函数”(即利用收盘后才确定的价格在盘中交易),策略逻辑通常基于上一根已完成的 K 线 (df.iloc[-2]) 的收盘价来判断是否突破。
  5. 交易函数 passorder:

    • 23 代表买入,24 代表卖出。
    • 1101 代表单股、单账号、按股数下单。
    • 5 代表使用最新价下单(市价单的一种模拟,实盘中可能需要根据流动性调整为对手价等)。

如何使用

  1. 打开 QMT 客户端,进入“模型研究”或“策略编辑器”。
  2. 新建一个 Python 策略。
  3. 将上述代码复制粘贴进去。
  4. 修改 ContextInfo.account_id 为你实际的资金账号。
  5. 点击“编译”检查是否有语法错误。
  6. 在回测模式下选择标的(如 600000.SH)和周期(如日线)进行回测。

Q&A

Q: 如何调整通道的灵敏度?
A: 修改 init 函数中的 ContextInfo.period (周期) 和 ContextInfo.multiplier (乘数)。

  • 增大 periodmultiplier 会使通道变宽,交易信号变少,但可能过滤掉震荡。
  • 减小它们会使通道变窄,信号变多,但可能面临更多假突破。

Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 的新版接口,返回的数据结构更规范(Pandas DataFrame),处理多只股票和多字段时更方便,且性能更好。

Q: 这个策略可以用于期货吗?
A: 逻辑上可以,但需要修改下单函数 passorder 的参数。期货涉及开仓(0/3)和平仓(1/2/4/5),且需要区分多空方向,而股票通常只有买入(23)和卖出(24)。