🚀 AI 一键生成 qmt 策略代码

如何让我的 QMT 量化策略更稳定?日志与错误处理最佳实践

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

问题描述

我的量化策略有时候会因为一些小错误而停止运行,如何才能让它更稳定,并且方便地记录下运行过程中的关键信息?

解决方案

为了提高 QMT 量化策略的稳定性并有效记录运行过程,主要需要从以下三个方面进行优化:异常捕获机制(Try-Except)持久化日志记录(Logging)以及数据防御性编程

以下是详细的解决方案和代码实现。

核心解决方案

  1. 引入异常捕获机制 (try...except)
    handlebar 等核心函数的逻辑外层包裹 try...except 语句。这样即使策略内部出现除以零、索引越界或数据获取失败等错误,策略也不会直接崩溃停止,而是捕获错误并继续运行下一个 Bar 或 Tick。

  2. 建立本地日志系统
    QMT 自带的界面输出窗口有行数限制且重启后会清空。建议在策略中编写一个写入本地 TXT 或 CSV 文件的函数,将交易信号、报错堆栈信息(Traceback)持久化保存到硬盘,方便盘后复盘和排查问题。

  3. 数据防御性检查
    在使用 get_market_data_ex 等接口获取数据后,务必先判断数据是否为空(None/Empty),再进行计算。


稳健的 QMT 策略模板代码

以下代码展示了一个包含自动容错本地日志记录功能的策略模板。你可以直接将其复制到 QMT 中作为基础框架。

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

# ===========================================
# 辅助功能:本地日志记录
# ===========================================
def write_log(ContextInfo, msg, level='INFO'):
    """
    将日志写入本地文件,防止QMT界面日志丢失
    """
    try:
        # 1. 确保日志目录存在
        if not hasattr(ContextInfo, 'log_path'):
            # 默认保存在 D盘 QMT_Logs 文件夹下,可根据需要修改
            ContextInfo.log_path = 'D:/QMT_Logs/'
            if not os.path.exists(ContextInfo.log_path):
                os.makedirs(ContextInfo.log_path)
        
        # 2. 生成文件名(按天生成)
        today_str = datetime.datetime.now().strftime('%Y%m%d')
        file_name = f"{ContextInfo.log_path}strategy_log_{today_str}.txt"
        
        # 3. 组装日志内容
        time_str = datetime.datetime.now().strftime('%H:%M:%S')
        log_content = f"[{time_str}] [{level}] {msg}\n"
        
        # 4. 写入文件 (追加模式)
        with open(file_name, 'a', encoding='gbk') as f:
            f.write(log_content)
            
        # 同时打印到QMT界面,方便实时查看
        print(log_content.strip())
        
    except:
        # 如果写日志都报错,只能打印到控制台了
        print(f"写入日志失败: {msg}")

# ===========================================
# 策略初始化
# ===========================================
def init(ContextInfo):
    try:
        # 设置账号
        ContextInfo.accid = 'YOUR_ACCOUNT_ID' # 请替换为真实账号
        ContextInfo.set_account(ContextInfo.accid)
        
        # 设置股票池
        ContextInfo.set_universe(['600000.SH', '000001.SZ'])
        
        # 初始化自定义变量
        ContextInfo.log_path = 'D:/QMT_Logs/' # 日志路径
        
        write_log(ContextInfo, "策略初始化成功", level='INFO')
        
    except Exception as e:
        write_log(ContextInfo, f"初始化失败: {traceback.format_exc()}", level='ERROR')

# ===========================================
# 核心行情驱动函数
# ===========================================
def handlebar(ContextInfo):
    # 使用 try-except 包裹整个核心逻辑
    try:
        # 1. 获取当前 K 线位置,避免在数据不足时计算
        index = ContextInfo.barpos
        realtime = ContextInfo.get_bar_timetag(index)
        
        # 2. 获取股票池
        stock_list = ContextInfo.get_universe()
        
        # 3. 获取数据 (使用防御性编程)
        # 获取最近 5 根 K 线数据用于计算均线
        data = ContextInfo.get_market_data_ex(
            ['close'], 
            stock_list, 
            period=ContextInfo.period, 
            count=5, 
            dividend_type='follow'
        )
        
        # --- 数据防御检查 ---
        if data is None or len(data) == 0:
            write_log(ContextInfo, "未获取到行情数据,跳过本次计算", level='WARNING')
            return

        # 4. 遍历股票进行逻辑处理
        for stock in stock_list:
            try:
                # 单个股票的逻辑也建议单独捕获异常,防止一只股票报错影响其他股票
                df = data.get(stock)
                
                # 检查该股票是否有数据
                if df is None or df.empty or len(df) < 5:
                    continue
                
                # 简单的策略逻辑示例:计算均线
                close_prices = df['close'].values
                ma5 = np.mean(close_prices)
                current_price = close_prices[-1]
                
                # 记录关键信息
                # write_log(ContextInfo, f"{stock} 最新价: {current_price:.2f}, MA5: {ma5:.2f}")
                
                # 交易逻辑 (示例)
                if current_price > ma5:
                    # 满足条件,执行操作...
                    pass
                    
            except Exception as inner_e:
                # 捕获单只股票处理时的错误
                write_log(ContextInfo, f"处理股票 {stock} 时发生错误: {str(inner_e)}", level='ERROR')
                continue

    except Exception as e:
        # 5. 捕获全局致命错误,并记录详细堆栈信息
        # traceback.format_exc() 能打印出具体是哪一行代码报错,非常重要
        error_msg = traceback.format_exc()
        write_log(ContextInfo, f"策略运行发生严重异常: \n{error_msg}", level='CRITICAL')

代码关键点解析

  1. traceback 模块

    • Python 标准库,用于获取详细的错误堆栈信息。
    • except 块中使用 traceback.format_exc(),可以精确地告诉你错误发生在第几行,是什么类型的错误(如 ZeroDivisionError, KeyError 等),这是调试的关键。
  2. write_log 函数

    • 它会自动在 D 盘创建一个 QMT_Logs 文件夹。
    • 它按日期生成日志文件(如 strategy_log_20231027.txt),避免文件过大。
    • 它同时将信息输出到 QMT 界面和本地文件,确保即使软件崩溃,本地文件也能保留最后时刻的报错信息。
  3. 双层 try...except 结构

    • 外层:包裹整个 handlebar,防止未知错误导致策略线程停止。
    • 内层for 循环内):包裹单只股票的处理逻辑。如果处理 "600000.SH" 时因为数据缺失报错,continue 会让程序继续处理 "000001.SZ",而不会因为一只股票的问题导致整个循环中断。
  4. 数据防御

    • if data is None or len(data) == 0: 这种判断在量化实盘中至关重要,因为网络波动或停牌都可能导致取不到数据。

Q&A: 常见问题解答

Q: 为什么我的策略在回测时没问题,实盘时却经常报错停止?
A: 回测使用的是清洗过的静态历史数据,通常比较完整。实盘面对的是实时数据流,可能会出现网络延迟导致的数据包丢失、交易所推送的临时异常值、或者某些冷门股票没有成交导致的数据为空(None)。因此,实盘代码必须包含对 None 值和空 DataFrame 的检查。

Q: traceback 会影响策略运行速度吗?
A: traceback 只有在发生错误并进入 except 块时才会被调用。在策略正常运行时,try...except 结构带来的性能损耗微乎其微(纳秒级),对于绝大多数中低频策略完全可以忽略不计。

Q: 日志文件写在 C 盘可以吗?
A: 建议写在 D 盘或其他非系统盘。现代 Windows 系统对 C 盘(尤其是 Program Files 目录)有严格的写入权限控制,如果 QMT 没有以管理员身份运行,写入 C 盘可能会因为权限不足而失败,导致报错。

Q: 如何查看 QMT 自身的系统报错?
A: 除了使用上述代码记录策略逻辑报错外,QMT 客户端底部通常有一个“日志”或“输出”面板,那里会显示系统级别的报错(如柜台连接断开、API 调用参数类型错误等)。结合本地日志和系统日志可以更全面地排查问题。