🚀 AI 一键生成 qmt 策略代码

如何在 QMT 中构建经典的 Dual Thrust 区间突破交易策略?

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

问题描述

请帮我实现一个Dual Thrust策略,当价格突破今天开盘价加上一定的波动范围时做多,跌破时做空。

解决方案

这是一个经典的 Dual Thrust 策略实现。Dual Thrust 是一种著名的趋势跟踪策略,它通过计算前 N 天的最高价、最低价和收盘价来确定一个波动区间(Range),然后结合当天的开盘价来确定上下轨。

策略逻辑

  1. 计算 Range (波动区间)
    利用前 N 日的历史数据计算:

    • $HH$ = N日内最高价的最高值
    • $HC$ = N日内收盘价的最高值
    • $LC$ = N日内收盘价的最低值
    • $LL$ = N日内最低价的最低值
    • $Range = \max(HH - LC, HC - LL)$
  2. 确定上下轨

    • 上轨 (Upper Bound) = 当日开盘价 + $K_1 \times Range$
    • 下轨 (Lower Bound) = 当日开盘价 - $K_2 \times Range$
  3. 交易信号

    • 当价格突破上轨时:做多(如果持有空单则平空开多)。
    • 当价格跌破下轨时:做空(如果持有多单则平多开空)。

QMT Python 代码实现

以下代码适用于 期货 交易(因为包含做空逻辑)。如果您用于股票,需要去掉做空部分,仅保留买入和卖出逻辑。

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

def init(ContextInfo):
    # --- 策略参数设置 ---
    ContextInfo.N = 5           # 回溯天数,用于计算Range
    ContextInfo.k1 = 0.5        # 上轨系数
    ContextInfo.k2 = 0.5        # 下轨系数
    ContextInfo.symbol = 'IF00.IF'  # 交易标的,这里以股指期货主力合约为例
    ContextInfo.account_id = 'YOUR_ACCOUNT_ID' # 请填写您的资金账号
    
    # 设置股票池
    ContextInfo.set_universe([ContextInfo.symbol])
    
    # 设置交易账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 初始化全局变量
    ContextInfo.pos = 0 # 简单的持仓状态记录:0空仓,1多头,-1空头

def handlebar(ContextInfo):
    # 获取当前K线位置,如果是回测模式,避免数据引用未来
    index = ContextInfo.barpos
    realtime = ContextInfo.get_bar_timetag(index)
    
    # 1. 获取历史行情数据 (取过去 N+1 天,因为要包含昨天及之前的N天)
    # 注意:我们需要的是"昨天"及之前的N天数据来计算Range,不包含"今天"
    # period='1d' 获取日线
    data = ContextInfo.get_market_data(
        ['high', 'low', 'close', 'open'], 
        stock_code=[ContextInfo.symbol], 
        period='1d', 
        count=ContextInfo.N + 1,
        dividend_type='none'
    )
    
    # 检查数据是否足够
    if ContextInfo.symbol not in data or len(data[ContextInfo.symbol]) < ContextInfo.N + 1:
        return

    df = data[ContextInfo.symbol]
    
    # 2. 数据处理
    # 截取前N天的数据(排除当前最新的这一根K线,因为最新的K线是今天正在走的)
    # 假设df的最后一行是当前日期(实盘中通常是这样),我们需要取倒数第2行往前推N行
    history_df = df.iloc[-(ContextInfo.N + 1):-1]
    
    # 获取今天的开盘价 (df的最后一行是今天)
    current_open = df.iloc[-1]['open']
    
    # 获取当前最新价格
    # 在实盘中,建议使用 get_full_tick 获取更实时的tick数据
    current_price = 0
    tick = ContextInfo.get_full_tick([ContextInfo.symbol])
    if tick and ContextInfo.symbol in tick:
        current_price = tick[ContextInfo.symbol]['lastPrice']
    else:
        # 如果获取不到tick,降级使用K线收盘价(回测时使用)
        current_price = df.iloc[-1]['close']

    # 3. 计算 Dual Thrust 的 Range
    hh = history_df['high'].max() # N日High的最高价
    hc = history_df['close'].max() # N日Close的最高价
    lc = history_df['close'].min() # N日Close的最低价
    ll = history_df['low'].min()   # N日Low的最低价
    
    # Range = Max(HH-LC, HC-LL)
    r_range = max(hh - lc, hc - ll)
    
    # 4. 计算上下轨
    upper_bound = current_open + ContextInfo.k1 * r_range
    lower_bound = current_open - ContextInfo.k2 * r_range
    
    # 打印调试信息 (可选)
    # print(f"Time: {realtime}, Price: {current_price}, Open: {current_open}, Range: {r_range}, Upper: {upper_bound}, Lower: {lower_bound}")

    # 5. 交易逻辑
    # 突破上轨,做多
    if current_price > upper_bound:
        if ContextInfo.pos != 1:
            # 如果持有空单,先平空
            if ContextInfo.pos == -1:
                print(f"价格突破上轨 {upper_bound},平空")
                buy_close_tdayfirst(ContextInfo.symbol, 1, 'LATEST', current_price, ContextInfo, ContextInfo.account_id)
            
            # 开多
            print(f"价格突破上轨 {upper_bound},开多")
            buy_open(ContextInfo.symbol, 1, 'LATEST', current_price, ContextInfo, ContextInfo.account_id)
            ContextInfo.pos = 1

    # 跌破下轨,做空
    elif current_price < lower_bound:
        if ContextInfo.pos != -1:
            # 如果持有多单,先平多
            if ContextInfo.pos == 1:
                print(f"价格跌破下轨 {lower_bound},平多")
                sell_close_tdayfirst(ContextInfo.symbol, 1, 'LATEST', current_price, ContextInfo, ContextInfo.account_id)
            
            # 开空
            print(f"价格跌破下轨 {lower_bound},开空")
            sell_open(ContextInfo.symbol, 1, 'LATEST', current_price, ContextInfo, ContextInfo.account_id)
            ContextInfo.pos = -1

# 辅助函数:简单的下单封装 (QMT自带了 buy_open 等函数,这里直接调用即可)
# 注意:实盘中建议配合 get_trade_detail_data 来判断真实持仓,而不是仅依赖 ContextInfo.pos 变量

代码关键点说明:

  1. 数据获取 (get_market_data)

    • 我们获取了 count = N + 1 根K线。
    • 关键逻辑:Dual Thrust 的 Range 是基于过去 N 天计算的,不包括今天。而今天的上下轨是基于今天的开盘价计算的。因此代码中 history_df 取的是 iloc[-(N+1):-1](过去N天),而 current_open 取的是 iloc[-1](今天)。
  2. 实时价格 (get_full_tick)

    • handlebar 中,如果是实盘运行,get_market_data 获取的最新收盘价可能滞后(取决于刷新频率),使用 get_full_tick 获取最新的 Tick 数据(lastPrice)能更灵敏地触发突破信号。
  3. 交易函数

    • 使用了 QMT 提供的便捷期货交易函数:
      • buy_open: 买入开仓(做多)
      • sell_close_tdayfirst: 卖出平仓(平多,优先平今)
      • sell_open: 卖出开仓(做空)
      • buy_close_tdayfirst: 买入平仓(平空,优先平今)
    • 下单价格使用了 'LATEST' (最新价),您也可以改为 'COMPETE' (对手价) 以提高成交率。
  4. 持仓管理 (ContextInfo.pos)

    • 代码中使用了一个简单的变量 pos 来记录策略状态。
    • 注意:在实盘长期运行时,建议使用 get_trade_detail_data(ContextInfo.account_id, 'future', 'position') 来查询真实的账户持仓,以防止策略重启后 pos 变量重置导致重复下单。

如何运行:

  1. 将代码复制到 QMT 的 Python 策略编辑器中。
  2. 修改 ContextInfo.account_id 为您的真实资金账号。
  3. 修改 ContextInfo.symbol 为您想交易的合约(如 IF2312.IF)。
  4. 点击“编译”并运行。如果是回测,请确保已下载对应的日线数据。