🚀 AI 一键生成 qmt 策略代码

QMT 平台利用 timetag_to_datetime 实现尾盘定时平仓

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

问题描述

怎么写一个策略在每天下午2点55分自动平仓?

解决方案

这是一个非常实用的风控或日内交易需求。在 QMT 中实现每天下午 14:55 自动平仓的策略,核心逻辑是在 handlebar 函数中监控当前时间,当时间到达指定时刻且当天尚未执行过平仓时,获取当前持仓并遍历下单平仓。

以下是完整的策略代码及详细说明。

策略代码

# -*- coding: gbk -*-
import time

def init(ContextInfo):
    """
    初始化函数
    """
    # ================= 配置区域 =================
    # 请在此处填写您的资金账号
    ContextInfo.account_id = '6000000000' 
    # 账号类型:'STOCK' (股票), 'FUTURE' (期货)
    ContextInfo.account_type = 'STOCK'
    # ===========================================

    # 设置交易账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 用于记录上一次执行平仓的日期,防止同一天重复触发
    ContextInfo.last_close_date = ''
    
    print("策略初始化完成,将在每天 14:55 执行自动平仓检查")

def handlebar(ContextInfo):
    """
    行情驱动函数,每个Tick或K线调用一次
    """
    # 只在实盘的最新一根K线(或最新Tick)执行,跳过历史K线回放
    if not ContextInfo.is_last_bar():
        return

    # 1. 获取当前时间
    # get_tick_timetag 获取的是毫秒级时间戳
    current_timetag = ContextInfo.get_tick_timetag()
    # 转换为 datetime 格式字符串
    current_datetime_str = timetag_to_datetime(current_timetag, '%Y%m%d%H%M%S')
    
    current_date = current_datetime_str[0:8]   # 年月日
    current_time = current_datetime_str[8:14]  # 时分秒

    # 2. 判断时间条件:是否到达 14:55:00 且当天未执行过
    # 注意:这里判断 >= 145500 且 < 150000 是为了确保在收盘前触发
    if current_time >= '145500' and current_time < '150000':
        
        # 检查今天是否已经执行过平仓
        if ContextInfo.last_close_date != current_date:
            print(f"当前时间 {current_time},触发自动平仓逻辑...")
            close_all_positions(ContextInfo)
            
            # 更新执行日期,标记今天已完成
            ContextInfo.last_close_date = current_date

def close_all_positions(ContextInfo):
    """
    自定义函数:平掉所有持仓
    """
    # 1. 获取持仓信息
    # 参数:账号ID, 账号类型, 数据类型('POSITION')
    positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
    
    if not positions:
        print("当前无持仓,无需平仓。")
        return

    print(f"检测到 {len(positions)} 只持仓,开始执行平仓...")

    # 2. 遍历持仓并下单
    for pos in positions:
        stock_code = pos.m_strInstrumentID  # 证券代码
        volume = pos.m_nCanUseVolume        # 可用持仓数量
        
        # 如果可用持仓为0,跳过
        if volume <= 0:
            continue

        print(f"正在平仓: {stock_code}, 数量: {volume}")

        # 3. 执行下单
        # opType=24: 股票卖出 (如果是期货,需根据多空方向选择 46(平今) 或 47(平昨))
        # orderType=1101: 单股单账号普通股数下单
        # prType=14: 对手价 (为了快速成交,也可以用 5-最新价 或 27-市价)
        # price=-1: 价格,使用对手价时填-1即可
        # quickTrade=1: 立即触发下单,不等待K线走完
        
        if ContextInfo.account_type == 'STOCK':
            passorder(24, 1101, ContextInfo.account_id, stock_code, 14, -1, volume, 'AutoClose', 1, '', ContextInfo)
        
        # 如果是期货,需要判断持仓方向
        elif ContextInfo.account_type == 'FUTURE':
            # m_nDirection: 48是多单,49是空单
            if pos.m_nDirection == 48: 
                # 平多 (优先平今 46, 也可以用 6)
                passorder(46, 1101, ContextInfo.account_id, stock_code, 14, -1, volume, 'AutoClose', 1, '', ContextInfo)
            elif pos.m_nDirection == 49:
                # 平空 (优先平今 48, 也可以用 8)
                passorder(48, 1101, ContextInfo.account_id, stock_code, 14, -1, volume, 'AutoClose', 1, '', ContextInfo)

    print("平仓指令发送完毕。")

代码核心逻辑解析

  1. 时间获取与判断

    • 使用 ContextInfo.get_tick_timetag() 获取当前最新的 Tick 时间戳。
    • 使用 timetag_to_datetime 将其转换为 HHMMSS 格式的字符串。
    • 判断条件 current_time >= '145500' 确保在下午 2 点 55 分及以后触发。
  2. 防止重复触发

    • 引入变量 ContextInfo.last_close_date
    • 每次触发平仓后,将当前日期记录下来。
    • 下一次 handlebar 运行时,如果发现 last_close_date 等于 current_date,说明今天已经执行过,直接跳过。
  3. 获取持仓

    • 使用 get_trade_detail_data(..., 'POSITION') 获取当前账户的所有持仓对象列表。
    • 通过 pos.m_nCanUseVolume 获取可用持仓(防止卖出已冻结或当日买入不可卖出的股票)。
  4. 下单平仓 (passorder)

    • 股票模式:使用 opType=24 (卖出)。
    • 报价类型:使用 prType=14 (对手价),这在尾盘急于平仓时比限价单更容易成交。
    • 快速交易:设置 quickTrade=1,这非常重要。默认情况下 QMT 是 K 线走完才下单,设置为 1 可以让信号在 Tick 到达时立即发出,确保 14:55 准时动作。

使用注意事项

  1. 运行周期:建议将策略运行在 1分钟线 (1m)Tick 周期上,以保证时间判断的灵敏度。
  2. 资金账号:请务必在 init 函数中修改 ContextInfo.account_id 为您真实的资金账号。
  3. 期货交易:代码中包含了一个简单的期货平仓分支(判断多空方向),如果您主要做期货,可能需要根据平今/平昨的手续费差异调整 opType
  4. 实盘模式:此策略依赖实时时间,请在“实盘”模式下运行。

Q&A

Q: 为什么使用 get_tick_timetag 而不是 Python 自带的 datetime.now()?
A: 在 QMT 回测或模拟中,datetime.now() 返回的是计算机系统当前时间,而 get_tick_timetag 返回的是行情数据的发生时间。使用 API 自带的时间函数可以保证策略在回测和实盘中的逻辑一致性,且更贴合行情驱动的机制。

Q: passorder 中的 quickTrade=1 是什么意思?
A: QMT 的默认机制是“K线走完确认信号后下单”。如果在日线周期运行,不加这个参数,策略会等到 15:00 收盘后才发出信号,导致无法成交。quickTrade=1 告诉系统只要代码运行到这一行,立即发送委托,不需要等待 K 线结束。

Q: 如果 14:55 停牌了怎么办?
A: passorder 发出委托后,如果股票停牌,交易所会拒单或废单。策略中的 pos.m_nCanUseVolume 只是本地判断可用持仓,无法预知交易所状态。如果需要更严谨,可以在下单前调用 ContextInfo.is_suspended_stock() 判断是否停牌。