AI Skill 给大 QMT 策略做代码评审(一):QMT 内置双均线策略

最近,我做了一件事:做成了一个 AI Agent Skill(名字叫 qmt-strategy-review),让它专门干一件事——评审大 QMT 策略代码+单线程工程分析,出一份可决策的审阅报告。

先说清楚状态:这个 Skill 还没上线,目前只在我本地的 Skill 目录里跑,等上架了我再更新。这篇就用一个真实案例(评审QMT 内置的双均线示例策略)展示它到底产出什么、怎么用、以及它明确不干什么。

真实案例:评审官方双均线示例

我把官方那份双均线实盘示例丢进去,跑出来的汇总表是这样:


第一部分:评审范围说明

本评审只检查代码编写与代码实现层:单线程阻塞、数据层效率、ContextInfo 误用与状态管理、API 签名与枚举值(均已逐一对照 QMT 官方文档核实)、编码声明与乱码风险、Python 代码层错误、回放与重复下单防护、日志与异常处理写法。

两点强调:

  • 不检查策略逻辑:双均线金叉/死叉信号是否有效、17/27 参数是否合理、盈亏与风险表现如何,属于策略逻辑范畴,本评审不评价、不预测、不下结论。下文提到“重复下单”指的是代码实现没有防护,不是对策略本身的判断。
  • 不收集、不存储策略代码:评审只在当前对话内完成——不落盘、不复制、不写入任何记忆或日志文件,阶段一连改后文件的磁盘副本都不生成。你贴出的代码只用于本次评审。

第二部分:修改意见

汇总表

编号 级别 位置 问题一句话
严1 严重 L58 get_history_data 的 dividend_type 传了字符串 ‘front_ratio’,官方定义为 int 0-4
警1 警告 L72-L80 waiting_list 只防”未查到委托”,废单后金叉条件持续成立会每个分笔循环报单
警2 警告 L38-L66 每个分笔执行 3 次柜台查询 + 全窗口均线重算,午休也空转,无降频
提1 提示 L43/L69 资金判断用 < 且未留手续费余量,接近临界会产生废单(与警1叠加放大)
提2 提示 L58 get_history_data 官方已标注【不推荐】,建议迁移 get_market_data_ex
提3 提示 L27/L40 print 输出账号明文,日志留档建议脱敏
通2 提示 L17-L18 account/accountType 是界面注入变量(静态扫描报未定义属预期),但仅限策略交易界面运行

总体结论:当前状态不建议直接上实盘。严1 可能直接异常或复权口径错误;最大风险是警1——一笔废单就能触发当日循环报单直到资金耗尽。

逐条详述

严1(严重)dividend_type 参数类型错误

  • 现象:C.get_history_data(..., dividend_type='front_ratio') 传了字符串。
  • 原因:官方文档定义 get_history_data 的 dividend_type 是 int:0=不复权、1=前复权、2=后复权、3=等比前复权、4=等比后复权。'front_ratio' 这种字符串风格是 get_market_data_ex 的参数体系,用在这里属于传错参数。
  • 依据:已经 QMT 官方文档核实(「行情函数」get_history_data 参数表)。
  • 改法:想要等比前复权就传 dividend_type=3;或按提2 整体迁移到 get_market_data_ex(它的 dividend_type 确实接受 'front_ratio' 字符串)。
  • 不改会怎样:可能每次调用抛异常(策略停摆),也可能静默按非预期复权口径取数——均线数值失真,信号跟着失真。改后要回归:取数长度、首尾收盘价与主图 K 线一致。

警1(警告)废单后循环报单

  • 现象:waiting_list 的语义是”委托在柜台查不到就暂停报单”。但委托一旦被查到(包括废单、已撤),就从列表移除,拦截解除。
  • 原因:买入委托若因资金不足/价格笼子等原因成为废单,持仓不会增加,而日线金叉条件在当天会持续满足——于是每个分笔都会重新 passorder,直到可用资金被反复冻结耗尽或收盘。这是典型的缺信号幂等问题。
  • 依据:已经 QMT 官方文档核实(「交易函数」委托查询机制与官方完整示例的“未查到委托 + 超时撤单重报 + 目标仓位对比”机制)。
  • 改法(局部示意,未落盘):增加当日已报方向标记(如 A.buy_today = True 做幂等),或参考官方完整示例,记录全部已报委托的 remark 并结合委托状态做状态机,废单后人工/定时器介入再决定重报。
  • 不改会怎样:一笔废单 → 当日几十上百笔重复委托,触犯超单限制还可能被券商风控关注;改后要回归:故意制造一笔废单,确认不会自动重报。

警2(警告)每分笔全量查询与重算,无降频

  • 现象:订阅行情后每个 tick 都会进 handlebar,每次都执行 3 次 get_trade_detail_data(account/order/position)+ 一次全窗口取数 + 4 次窗口均值计算;11:30-13:00 午休期间也在空转;print 未查到委托时每分笔刷屏。
  • 原因:只过滤了历史 K 线(is_last_bar)和交易时段,没有”这根日线走完了没”的判定。
  • 依据:已经 QMT 官方文档核实(「系统函数」is_new_bar 释义;官方完整示例采用 run_time 定时器模式)。
  • 改法:日线信号建议加 C.is_new_bar() 做“收根结算”,或整体改为 run_time 定时器轮询(官方完整示例即此模式);print 加限流。
  • 不改会怎样:单只品种尚可容忍,但这是同一客户端所有策略共享的线程开销,多策略叠加时大家一起变慢;改后要回归:信号触发时点是否符合预期(注意 is_new_bar 会把信号挪到收根后,语义有变化,需你确认想要盘中即时还是收根确认)。

提1(提示)资金边界:A.amount < available_cash 用 <(恰好等于时跳过买入),且 int() 截断、未预留手续费——金额贴近可用资金时容易因费用不足产生废单,正好踩中警1的循环报单。可改为类似 A.amount <= available_cash * 0.998 留出费用余量。

提2(提示)API 迁移:get_history_data 官方已标注【不推荐】,推荐 get_market_data_ex,且后者的 dividend_type 接受字符串 'front_ratio'——一次迁移可同时消掉严1。候选写法见第三部分注释。

提3(提示)日志脱敏:L27、L40 直接 print 账号,建议 '****' + A.acct[-4:] 之类的脱敏写法。

通2(提示)界面注入变量:account/accountType 是策略交易界面运行时注入的内置变量(官方示例同样直接使用),静态扫描报“未定义”属预期误报;但在编辑器/回测环境运行会 NameError——这段代码只能在策略交易界面以实盘/模拟方式运行。

已核实无误的部分(正面确认,无需改):

  • 状态存模块级 A 实例而非 ContextInfo,正确规避了深拷贝回滚问题,L13 注释的理解也是对的;
  • C.stockcode + '.' + C.market 拼接格式正确(stockcode 返回不带市场的代码,market 返回市场标识);
  • passorder 参数:opType 23/24(普通买卖)、33/34(担保品买卖)、orderType 1101(单股单账号按股数)、prType 14(对手价)、price=-1、quickTrade=1 配合 is_last_bar() 过滤——全部与官方文档一致;
  • get_trade_detail_data 三处调用的账号/类型/数据类型参数正确,所用字段 m_dAvailable/m_strRemark/m_strInstrumentID/m_strExchangeID/m_nCanUseVolume 均与官方数据结构一致;
  • 无 sleep、无线程库、无全量取数——单线程五条铁律全部未触碰。

第三部分:节选代码 + 注释(原样,逻辑零改动)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#encoding:gbk
# [qmt-strategy-review] ============ 评审(阶段一,未修改任何逻辑)============
# [qmt-strategy-review][通2][提示] account / accountType 是策略交易界面注入的内置变量,
# 静态扫描报"未定义"属预期; 编辑器/回测环境运行会 NameError, 仅限策略交易界面
import pandas as pd
import numpy as np
import datetime

"""
示例说明:双均线实盘策略,通过计算快慢双均线,在金叉时买入,死叉时做卖出
"""

class a():
pass
A = a() #创建空的类的实例 用来保存委托状态
#ContextInfo对象在盘中每次handlebar调用前都会被深拷贝, 如果调用handlebar的分笔不是k线最后分笔 ContextInfo会被回退到深拷贝的内容 所以ContextInfo不能用来记录快速交易的信号
# [qmt-strategy-review] 优点: 状态存模块级 A 实例而非 ContextInfo, 符合深拷贝回退机制的规范写法

def init(C):
# [qmt-strategy-review] 已核实: C.stockcode 返回不带市场的代码, C.market 返回市场标识,
# 拼接结果为标准 'code.market' 格式, 写法正确 (依据: 官方文档「变量约定」)
A.stock= C.stockcode + '.' + C.market #品种为模型交易界面选择品种
A.acct= account #账号为模型交易界面选择账号
A.acct_type= accountType #账号类型为模型交易界面选择账号
# [qmt-strategy-review] 已核实: 23/24 普通买卖, 33/34 担保品买卖, 与官方枚举一致
# (依据: 官方文档「枚举常量」opType)
A.buy_code = 23 if A.acct_type == 'STOCK' else 33 #买卖代码 区分股票 与 两融账号
A.sell_code = 24 if A.acct_type == 'STOCK' else 34
A.amount = 10000 #单笔买入金额 触发买入信号后买入指定金额
A.line1=17 #快线周期
A.line2=27 #慢线周期
A.waiting_list = [] #未查到委托列表 存在未查到委托情况暂停后续报单 防止超单
#设置股票池 订阅品种行情
C.set_universe([A.stock])
# [qmt-strategy-review][提3] print 输出账号明文, 日志留档建议脱敏 (如 '****'+A.acct[-4:])
print(f'双均线实盘示例{A.stock} {A.acct} {A.acct_type} 单笔买入金额{A.amount}')

def handlebar(C):
#跳过历史k线
if not C.is_last_bar():
return
now = datetime.datetime.now()
now_time = now.strftime('%H%M%S')
#跳过非交易时间
if now_time < '093000' or now_time > "150000":
return
# [qmt-strategy-review][警2] 每个分笔执行 3 次柜台查询 + 全窗口均线重算, 午休时段也在空转
# 原因: 订阅行情后每个 tick 都进 handlebar, 无"这根日线走完了没"的判定
# 依据: QMT 官方文档「系统函数」is_new_bar; 官方完整示例采用 run_time 定时器模式
# 改法: 加 C.is_new_bar() 收根结算, 或改 run_time 定时器轮询; print 加限流
# 注意: is_new_bar 会把信号挪到收根后, 盘中即时 vs 收根确认需你确认想要哪种

## 策略代码全文和审阅完整版报告请在后台发送“评审”获取。

# [qmt-strategy-review] ============ 评审结束:以上均为注释意见,代码逻辑未做任何改动 ============

是否需要我落地修改? 如需进入阶段二(届时会先备份 双均线.txt.bak,只改确认过的条目,改完重跑静态审查对比),请回复条目编号,例如「改 通2、严1、警1」或「全改」。


⚠️ 实盘风险提示:涉及真实下单。请先在 QMT「模拟信号模式」或「模拟柜台」中充分验证后,再切换实盘账号;股票受 2% 价格笼子限制,数量超可用会产生废单。


⚠️ 风险提醒与免责声明

  • 本回答基于 QMT 官方公开文档与公开社区资料整理,仅供编程学习与技术研究,不构成任何投资建议。
  • 实盘前必须在「模拟信号模式」或「模拟柜台」充分验证;未经模拟验证直接实盘,后果自负。
  • 具体行为以官方文档、券商柜台与本地客户端实际输出为准,不承诺任何委托结果。
  • 因使用本回答中的代码或建议造成的任何盈亏,由使用者自行承担全部责任。


要提醒的是别指望它做的事:它不告诉你能不能赚钱,也不替你决定参数。它给的是”这段代码在工程上站不站得住”,剩下的判断还是你自己的。而且它的输出是 AI 生成的,每条都标了依据,但你最好还是对着官方文档核一眼关键的那几条——尤其是涉及下单的部分。

现状

qmt-strategy-review 目前尚未上线,我是在本地 Skill 目录里装的,配合 QMT 官方 API 文档做查档。等它上架到 Skill 市场我再更新这篇。如果你也在做 QMT 策略,可以留意一下;在那之前,上面这份报告里的七条问题清单本身,也够你拿去对照自己的代码自查一遍了。

策略代码全文和审阅完整版报告请在后台发送“代码评审”获取。


免责声明

  • 本文为 AI 工具使用心得与技术分享,仅供编程学习与技术研究,不构成任何投资建议、荐股意见或收益承诺。
  • 文中评审意见由 AI 生成,虽已标注官方文档依据,仍可能存在误判;请以 QMT 官方文档与你本地客户端的实际行为为准。
  • 涉及的 passorder 等交易接口会在实盘模式下真实发出委托,任何策略在实盘前必须先在 QMT「模拟信号模式」或「模拟柜台」中充分验证。
  • 因使用本文内容、代码或建议造成的任何盈亏,由使用者自行承担全部责任。量化交易涉及真实资金风险,入市需谨慎。
  • QMT、迅投 为迅投公司或其关联公司的商标,本文为社区非官方整理,与官方无隶属关系。
  • 本文由 AI 创作,请自行判断是否适用。