🚀 AI 一键生成 qmt 策略代码

QMT 策略如何优雅停止?stop 函数使用详解

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

问题描述

在我的量化策略中,如果启动了额外的线程,如何在策略停止时安全地关闭它们?

解决方案

在 QMT 量化平台中,如果您在策略中启动了额外的 Python 线程(例如用于执行耗时的计算、监控外部信号或异步IO操作),必须确保在策略停止时能够优雅地关闭这些线程,以防止资源泄露或后台残留进程。

QMT 提供了一个专门的生命周期函数 stop(ContextInfo) 来处理策略退出时的清理工作。

核心解决方案

  1. 使用 stop(ContextInfo) 函数:这是 QMT 专门用于策略停止前执行清理工作的回调函数。
  2. 使用标志位控制线程循环:在 ContextInfo 对象中设置一个布尔标志位(例如 ContextInfo.is_running),线程内部循环检测该标志。
  3. stop 中修改标志并回收线程:当策略停止时,QMT 会自动调用 stop 函数。您需要在该函数中将标志位设为 False,并调用 thread.join() 等待线程安全退出。

代码实现示例

以下是一个完整的策略代码示例,展示了如何在 init 中启动线程,并在 stop 中安全关闭它。

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

def my_custom_task(ContextInfo):
    """
    这是运行在子线程中的自定义任务函数
    """
    print("子线程: 启动成功")
    
    # 循环检查 ContextInfo.is_running 标志位
    while ContextInfo.is_running:
        # 执行您的后台任务逻辑
        print(f"子线程: 正在运行... 当前时间: {time.strftime('%H:%M:%S')}")
        
        # 模拟耗时操作,注意这里要有 sleep,避免死循环占用过多 CPU
        time.sleep(2)
        
    print("子线程: 检测到停止信号,正在退出...")

def init(ContextInfo):
    """
    策略初始化函数
    """
    print("策略初始化: init")
    
    # 1. 设置线程运行控制标志位,挂载到 ContextInfo 上以便全局访问
    ContextInfo.is_running = True
    
    # 2. 创建线程,将 ContextInfo 作为参数传递进去
    # 注意:daemon=False 表示非守护线程,我们需要手动管理它的生命周期
    ContextInfo.worker_thread = threading.Thread(target=my_custom_task, args=(ContextInfo,))
    
    # 3. 启动线程
    ContextInfo.worker_thread.start()
    print("策略初始化: 线程已启动")

def handlebar(ContextInfo):
    """
    K线/Tick驱动函数
    """
    # 这里是主线程逻辑
    if ContextInfo.is_new_bar():
        print(f"主线程: handlebar 运行中... BarPos: {ContextInfo.barpos}")

def stop(ContextInfo):
    """
    策略停止函数:当用户点击停止或关闭策略时触发
    """
    print("策略停止: stop 被调用")
    
    # 1. 修改标志位,通知子线程退出循环
    if hasattr(ContextInfo, 'is_running'):
        ContextInfo.is_running = False
    
    # 2. 等待子线程结束 (join)
    # 设置 timeout 防止子线程卡死导致主程序无法退出
    if hasattr(ContextInfo, 'worker_thread') and ContextInfo.worker_thread.is_alive():
        print("策略停止: 等待子线程退出...")
        ContextInfo.worker_thread.join(timeout=5)
        print("策略停止: 子线程已关闭")
    
    print("策略停止: 资源清理完毕")

关键点解析

  1. ContextInfo 的作用
    ContextInfo 是贯穿整个策略生命周期的全局对象。我们将线程对象 (worker_thread) 和控制标志 (is_running) 都挂载在 ContextInfo 上,这样在 inithandlebarstop 函数中都能访问到它们。

  2. stop(ContextInfo) 函数
    这是 QMT API 文档中明确规定的接口。当您在界面上点击“停止运行”或者关闭策略编辑器时,底层 C++ 框架会调用 Python 的 stop 函数。如果您的策略涉及文件句柄关闭、数据库连接断开或线程停止,必须写在这个函数里。

  3. 线程安全与阻塞

    • stop 函数中使用 join() 是为了确保子线程完全执行完当前的逻辑块并退出。
    • 建议给 join(timeout=5) 加上超时时间。如果子线程因为某些原因(如网络IO阻塞)无法响应停止信号,超时机制可以防止整个 QMT 客户端界面卡死。
  4. 避免在子线程中直接交易
    QMT 的交易函数(如 passorder)通常不是线程安全的,且必须由主线程调用。建议子线程只负责计算或数据处理,将结果写入 ContextInfo 的某个变量,然后在 handlebar(主线程)中检测该变量并执行下单操作。


Q&A

Q: 如果我不写 stop 函数会怎么样?
A: 如果不实现 stop 函数或不在其中关闭线程,当您在 QMT 界面停止策略时,Python 的主线程会结束,但您启动的子线程(如果是非守护线程)可能会继续在后台运行。这会导致日志持续输出、占用 CPU 资源,甚至在重新运行策略时导致逻辑冲突(例如多个线程同时写入同一个文件)。

Q: ContextInfo 对象在子线程中是线程安全的吗?
A: Python 的 GIL(全局解释器锁)在一定程度上保证了基本操作的原子性,但在并发读写复杂数据结构(如 List 或 Dict)时,建议使用 threading.Lock 进行保护。特别是当子线程写入数据,主线程 handlebar 读取数据时,加锁是最佳实践。

Q: 能否使用 thread.terminate() 强制杀死线程?
A: Python 标准库 threading 模块不支持强制终止线程。强制终止极易导致资源(如锁、文件句柄)无法释放,从而引发死锁或数据损坏。使用“标志位 + 循环检测”的软退出方式是唯一推荐的安全做法。