🚀 AI 一键生成 qmt 策略代码

捕捉开盘跳空缺口:日内缺口回补策略实现

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

问题描述

如何编写日内跳空缺口回补策略?

解决方案

这是一个关于**日内跳空缺口回补(Gap Filling)**策略的实现方案。

策略逻辑概述

缺口回补策略基于“均值回归”的思想。当市场开盘价格显著偏离前一交易日的收盘价(形成缺口)时,价格往往有动力向昨收价回归,以“回补”这个缺口。

  1. 跳空低开(Gap Down):
    • 条件:今日开盘价 < 昨日收盘价 × (1 - 阈值)。
    • 行为:买入
    • 止盈:价格回升至昨日收盘价附近。
    • 止损:价格继续下跌超过一定幅度。
  2. 跳空高开(Gap Up):
    • 条件:今日开盘价 > 昨日收盘价 × (1 + 阈值)。
    • 行为:卖出(股票需有底仓,或用于期货/融券)。
    • 止盈:价格回落至昨日收盘价附近。
    • 止损:价格继续上涨超过一定幅度。
  3. 日内平仓: 无论盈亏,在收盘前(如14:55)强制平仓,保持日内交易特性。

注意事项(A股 T+1 限制)

  • 股票交易: 中国A股实行 T+1 制度。如果你当天买入,当天无法卖出。因此,纯日内回补策略在A股通常需要“底仓”(即账户里已有该股票,通过买入新仓、卖出旧仓来实现T+0),或者应用于期货、可转债、ETF等支持T+0的品种。
  • 本代码示例: 为了演示清晰,我将编写一个**做多回补(跳空低开买入,尾盘或回补后卖出)**的逻辑。如果你用于股票,请确保你理解T+1的限制;如果你用于期货,逻辑是通用的。

QMT 策略代码实现

# -*- coding: gbk -*-
import pandas as pd
import time

def init(ContextInfo):
    # ================= 策略参数设置 =================
    # 交易账号 (请修改为您的实际账号)
    ContextInfo.account_id = '6000000000' 
    # 账号类型:'STOCK'股票, 'FUTURE'期货
    ContextInfo.account_type = 'STOCK'
    
    # 策略运行标的 (示例:平安银行)
    ContextInfo.stock_code = '000001.SZ'
    
    # 缺口阈值 (例如 1% = 0.01)
    ContextInfo.gap_threshold = 0.01
    
    # 止损阈值 (例如亏损 2% 止损)
    ContextInfo.stop_loss_pct = 0.02
    
    # 单笔交易数量
    ContextInfo.trade_volume = 100
    
    # ================= 状态变量 =================
    # 标记今日是否已经开仓
    ContextInfo.has_opened = False
    # 记录开仓价格
    ContextInfo.open_trade_price = 0.0
    # 记录目标回补价格(即昨收价)
    ContextInfo.target_price = 0.0
    
    # 设置股票池
    ContextInfo.set_universe([ContextInfo.stock_code])
    
    # 绑定账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    print("策略初始化完成:日内缺口回补策略")

def handlebar(ContextInfo):
    # 获取当前K线的时间
    index = ContextInfo.barpos
    timetag = ContextInfo.get_bar_timetag(index)
    # 将时间戳转换为 datetime 对象以便提取小时和分钟
    bar_time_str = timetag_to_datetime(timetag, '%H%M%S')
    bar_time_int = int(bar_time_str)
    
    # 获取当前最新价格
    current_price = ContextInfo.get_market_data(['close'], stock_code=[ContextInfo.stock_code], period='1d', count=1, dividend_type='follow')
    # 注意:get_market_data 返回的是 pandas Series/DataFrame,需要取值
    if isinstance(current_price, (pd.Series, pd.DataFrame)) and not current_price.empty:
        # 这里取的是当前bar的收盘价作为最新价
        last_price = ContextInfo.get_market_data(['close'], stock_code=[ContextInfo.stock_code], period='1m', count=1, dividend_type='follow')
        if isinstance(last_price, pd.DataFrame):
             current_price = last_price.iloc[-1]['close']
        else:
             return # 数据获取失败
    else:
        return

    # ================= 1. 开盘检测缺口 (09:30 - 09:35) =================
    # 每天只开仓一次
    if not ContextInfo.has_opened and 93000 <= bar_time_int <= 93500:
        
        # 获取日线数据:取最近2根日线(今天和昨天)
        # index 0 是昨天,index 1 是今天(包含今天的开盘价)
        daily_data = ContextInfo.get_market_data_ex(
            ['open', 'close', 'preClose'], 
            [ContextInfo.stock_code], 
            period='1d', 
            count=1, 
            dividend_type='follow'
        )
        
        if ContextInfo.stock_code in daily_data:
            df_daily = daily_data[ContextInfo.stock_code]
            if not df_daily.empty:
                # 获取今日开盘价
                today_open = df_daily.iloc[-1]['open']
                # 获取昨日收盘价 (preClose 字段通常比取上一根K线close更准确)
                yesterday_close = df_daily.iloc[-1]['preClose']
                
                ContextInfo.target_price = yesterday_close
                
                # 计算缺口幅度
                gap_pct = (today_open - yesterday_close) / yesterday_close
                
                # --- 策略逻辑:跳空低开 (Gap Down) 买入 ---
                if gap_pct < -ContextInfo.gap_threshold:
                    print(f"检测到跳空低开: 开盘{today_open}, 昨收{yesterday_close}, 幅度{gap_pct:.2%}")
                    
                    # 执行买入
                    passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_volume, ContextInfo)
                    
                    ContextInfo.has_opened = True
                    ContextInfo.open_trade_price = current_price
                    print(f"买入开仓: 价格 {current_price}, 数量 {ContextInfo.trade_volume}")

    # ================= 2. 盘中监控 (止盈/止损) =================
    if ContextInfo.has_opened:
        # 止盈条件:价格回升到昨日收盘价(缺口回补)
        if current_price >= ContextInfo.target_price:
            print(f"缺口回补成功: 当前价{current_price} >= 昨收{ContextInfo.target_price}")
            close_position(ContextInfo)
            
        # 止损条件:价格继续下跌超过止损线
        elif current_price <= ContextInfo.open_trade_price * (1 - ContextInfo.stop_loss_pct):
            print(f"触发止损: 当前价{current_price}, 成本{ContextInfo.open_trade_price}")
            close_position(ContextInfo)

    # ================= 3. 尾盘强制平仓 (14:55) =================
    if ContextInfo.has_opened and bar_time_int >= 145500:
        print("尾盘强制平仓")
        close_position(ContextInfo)
        
    # ================= 4. 收盘重置状态 (15:00 后) =================
    # 如果是回测,可能需要手动重置;实盘中第二天init会重新运行,或者在这里判断日期变化
    if bar_time_int >= 150000:
        ContextInfo.has_opened = False

def close_position(ContextInfo):
    """
    平仓函数
    """
    # 卖出平仓 (股票卖出操作码 24)
    passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_volume, ContextInfo)
    
    print(f"执行平仓: {ContextInfo.stock_code}")
    
    # 重置开仓状态,防止今日再次开仓
    ContextInfo.has_opened = False
    # 标记为今日已完成交易,避免重复开仓(如果需要严格控制)
    # 在此简单逻辑中,has_opened = False 会导致如果还在9:35之前可能再次开仓,
    # 实际应用中建议增加一个 ContextInfo.traded_today 变量来控制一天只做一次。

代码关键点解析

  1. 数据获取 (get_market_data_ex):

    • 我们使用 period='1d' 获取日线数据。这是因为我们需要准确的“今日开盘价”和“昨日收盘价”。
    • preClose 字段是交易所提供的昨收价,比自己去取上一根K线的 close 更稳健(处理了除权除息等情况)。
  2. 时间控制:

    • timetag_to_datetime 用于将毫秒时间戳转换为易读的格式(如 093000)。
    • 开仓窗口:限制在 09:3009:35 之间,确保是针对开盘缺口的交易。
    • 尾盘平仓:设置在 14:55,确保在收盘前平掉仓位(如果是T+0品种)。
  3. 交易逻辑:

    • 跳空低开gap_pct < -阈值。逻辑是市场反应过度,预期反弹。执行买入。
    • 止盈:当 current_price >= yesterday_close 时,说明缺口已补,任务完成,平仓。
  4. 下单函数 (passorder):

    • 23: 股票买入。
    • 24: 股票卖出。
    • 1101: 单股、单账号、普通、股/手方式下单。
    • 5: 最新价报价(也可以用 14 对手价以保证成交)。

如何在 QMT 中运行

  1. 打开 QMT 软件,进入【模型研究】或【策略编辑器】。
  2. 新建一个 Python 策略。
  3. 将上述代码复制进去。
  4. 修改账号:将 ContextInfo.account_id 修改为你自己的实盘或模拟盘账号。
  5. 修改标的:将 ContextInfo.stock_code 修改为你想交易的股票或ETF代码。
  6. 设置周期:在界面上将运行周期设置为 1分钟5分钟(因为需要在盘中实时监控价格变化)。
  7. 点击【编译】检查无误后,可以进行【回测】或【运行】。

常见问题 Q&A

Q: 为什么我回测时没有交易?
A:

  1. 检查回测时间段内是否有满足 1% (0.01) 缺口的行情。
  2. 检查 ContextInfo.stock_code 是否在你的数据下载范围内。
  3. 确保回测的主图周期是分钟线(如1m),而不是日线。如果在日线周期运行 handlebar,一天只运行一次,无法实现日内平仓。

Q: 如何做空(跳空高开卖出)?
A:
如果是期货,将买入代码改为 opType=3 (开空),平仓改为 opType=4 (平昨) 或 5 (平今)。
如果是股票,你需要持有底仓。在开仓逻辑中,判断 gap_pct > 阈值,然后执行 passorder(24, ...) 卖出;在止盈逻辑(价格跌回昨收)时,执行 passorder(23, ...) 买回。

Q: get_market_dataget_market_data_ex 有什么区别?
A: get_market_data_ex 是新版接口,返回的数据结构更清晰(通常是 Dict of DataFrames),且支持订阅逻辑,建议优先使用。代码中混用了两者是为了展示不同场景,核心逻辑依赖 ex 接口获取日线数据。