问题描述
解决方案
为了提高 QMT 量化策略的稳定性并有效记录运行过程,主要需要从以下三个方面进行优化:异常捕获机制(Try-Except)、持久化日志记录(Logging)以及数据防御性编程。
以下是详细的解决方案和代码实现。
核心解决方案
-
引入异常捕获机制 (
try...except):
在handlebar等核心函数的逻辑外层包裹try...except语句。这样即使策略内部出现除以零、索引越界或数据获取失败等错误,策略也不会直接崩溃停止,而是捕获错误并继续运行下一个 Bar 或 Tick。 -
建立本地日志系统:
QMT 自带的界面输出窗口有行数限制且重启后会清空。建议在策略中编写一个写入本地 TXT 或 CSV 文件的函数,将交易信号、报错堆栈信息(Traceback)持久化保存到硬盘,方便盘后复盘和排查问题。 -
数据防御性检查:
在使用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')
代码关键点解析
-
traceback模块:- Python 标准库,用于获取详细的错误堆栈信息。
- 在
except块中使用traceback.format_exc(),可以精确地告诉你错误发生在第几行,是什么类型的错误(如ZeroDivisionError,KeyError等),这是调试的关键。
-
write_log函数:- 它会自动在 D 盘创建一个
QMT_Logs文件夹。 - 它按日期生成日志文件(如
strategy_log_20231027.txt),避免文件过大。 - 它同时将信息输出到 QMT 界面和本地文件,确保即使软件崩溃,本地文件也能保留最后时刻的报错信息。
- 它会自动在 D 盘创建一个
-
双层
try...except结构: -
数据防御:
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 调用参数类型错误等)。结合本地日志和系统日志可以更全面地排查问题。