恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
QMT自动化下单实战:环境搭建、委托流程与风控全解析
首页
资讯中心
/
QMT自动化下单实战:环境搭建、委托流程与风控全解析
QMT自动化下单实战:环境搭建、委托流程与风控全解析
发布时间:2026/10/17 19:10:38
简介QMT自动化下单指南代码包面向量化交易开发者重点演示迅投QMT极简版下使用XtQuant模块完成自动化下单的完整流程。内容覆盖XtQuant.XtData行情模块与Xttrader交易模块的调用示例并明确列出支持的Python版本与开发接口帮助读者快速搭建从实时行情获取到策略落地的技术链路。压缩包共4个文件以Python脚本为核心辅以txt环境说明、inscode工程及html操作指南整体仅9KB结构轻量但浓缩了关键开发环节。目前已有371人学习下载适合初步接触QMT、希望用Python实现自动交易下单的开发者作为入门参考。通过学习示例代码和方法说明可直观理解极简版QMT的模块组织方式掌握数据读取、交易下单及环境配置的具体写法减少自行摸索与查阅文档的时间成本。1. QMT 自动化下单代码方案到底该怎么落地先看懂这三件事QMT 自动化下单对做国内股票和 ETF 的个人量化来说几乎快成“默认选项”了。它把行情、资金、持仓、委托、成交回报全部暴露给一个本地 Python 库 XtQuant让策略程序自己完成“出信号 → 下委托 → 收回报 → 更新仓位”的闭环不需要人盯盘。可大多数第一次动手的人会卡在三个地方搞混本地 Python 和 QMT 自带托管环境的区别委托发出后不知道订单到底走到哪一步缺少代码之外的风控一出问题就是一连串失控单。这篇文章就从环境搭建写到第一笔自动委托再到能持续运行的订单流程附上可直接改的 Python 代码和上线前的高频坑。适合已有买卖信号、想省去手动下单动作的交易者也适合准备从回测搬到模拟盘的开发者。策略信号本身不在这篇范围内专注“下单”这条自动化链路的落地。2. 搭建 QMT 自动化下单环境客户端、Python 与最小验证代码在你写第一行业务逻辑之前环境里最大的坑其实已经埋好了QMT 的 Python 接口有两种截然不同的消费方式很多人第一步就站错了队后面所有报错都解释不通。先把运行模式确定下来再谈下单代码。2.1 先分三种运行模式本地 Python、托管策略与客户端外挂最常见的做法是本地 Python 模式策略代码跑在你自己的 Python 进程里QMT 客户端保持登录状态、作为连接对象Python 通过 XtQuant 和它建立本地通信。这个模式最灵活读取本地文件、连数据库、调用 pandas、numpy 都不受限制调试也方便是本篇文章所有代码假设的运行模式。第二种是 QMT 客户端内嵌的托管策略模式把 .py 文件放到客户端指定的策略目录由客户端自带的解释器启动运行。这种方式更接近部署态但本地文件读取受限遇到“国金 QMT 是否允许读本地文件”这类问题时多半就是在这种模式里踩到的托管环境为了隔离不会让策略随意读写任意路径。第三种是客户端外挂模式把策略独立封装成另一个可执行程序通过 QMT 提供的行情/交易接口间接工作。它本质上还是需要 QMT 客户端在后台运行适合已有一整套外部系统的团队。对个人而言先走本地 Python 模式能避免掉大部分环境层面的玄学问题。2.2 配置前检查清单券商版本、资金账号与 Python 环境本地 Python 模式也不是装上就能跑。按我排障的顺序先把这几项过一遍检查项要求说明QMT 客户端已安装且能正常登录交易账户只是行情登录不够交易通道必须就绪资金账号券商开通 QMT 权限后给的资金账号不是证券账号是一串数字资金账号Python 版本建议 3.7 到 3.10x tquant 的 C 扩展对 Python 版本敏感xtquant 库能从 QMT 安装目录 import一般在安装目录的 bin\xtquant 子目录下如果你的机器上 Python 版本过高调用 xtquant 时可能出现 DLL 加载失败或进程直接崩溃这不代表代码写错了换到配套版本通常能解决。另一个容易漏的是本地模式虽然能读写本地文件但你得先确认策略进程的工作目录有权限别把配置文件放在需要管理员权限的系统目录里。2.3 最小连通性代码连接客户端、订阅账户、查一次资产配置确认完先跑一段最短的连通性代码不要直接写下单逻辑。这段代码只做三件事建立连接、订阅资金账号、查一次资产。from xtquant import xttrader from xtquant.xttype import StockAccount import time # 1. 创建交易对象路径指向 QMT 安装目录下的同名资源目录 path rD:\qmt\bin\xt # 改成你本机实际路径 session_id int(time.time()) # 每次启动用不同的会话 id xt_trader xttrader.XtQuantTrader(path, session_id) # 2. 启动后台线程并连接到正在运行的 QMT 客户端 xt_trader.start() connect_result xt_trader.connect() print(连接结果, connect_result) # 0 表示成功非 0 需要排查 # 3. 订阅资金账号后面所有委托和成交回报都依赖这一步 account StockAccount(资金账号, STOCK) # 替换成你的资金账号 sub_result xt_trader.subscribe(account) print(订阅结果, sub_result) # 4. 查一次资产验证交易链路真的通了 asset xt_trader.query_stock_asset(account) print(总资产, asset.cash asset.market_value) print(可用资金, asset.cash)逻辑说明path指向 QMT 安装目录里与 Python 接口对应的那一层不同券商给的安装结构不一样填错时 connect 会直接失败。session_id不建议写死它用来区分同一时刻的多个策略进程重复使用会被客户端当成同一个会话可能出现回报串线。参数说明subscribe(account)是把该资金账号的委托、成交、持仓变化绑定到当前 Python 进程。很多人跳过这一步结果 order_stock 能返回委托编号但回调里什么都收不到。最后一步query_stock_asset返回的对象里cash是可用资金market_value是持仓市值两者相加才是总资产。2.4 链路不通时按什么顺序排查connect 返回非 0按这个顺序查QMT 客户端是否已经启动并登录交易账户path是否指向正确的安装目录本机是否同时跑了多个策略进程导致会话冲突。connect 返回 0 但query_stock_asset返回 None说明交易通道还没就绪。常见原因是客户端登录后没有完成“交易初始化”或者账号订阅失败。这一步没办法用代码绕过回客户端界面确认交易账户状态为已就绪再重跑。3. 用 order_stock 完成第一笔自动委托参数与价格类型怎么选链路通了之后第一笔自动委托不建议直接上你真实的交易逻辑而是用一个明确的小单验证参数。下单 API 的核心就一个函数 order_stock这一章把它的每个参数拆开讲清楚再补上价格类型的选择思路。3.1 order_stock 参数逐项拆解先看一段最小买入代码from xtquant.xtconstant import STOCK_BUY, STOCK_SELL, FIX_PRICE order_id xt_trader.order_stock( account, # StockAccount 对象且已经 subscribe 600000.SH, # 股票代码必须带交易所后缀 STOCK_BUY, # 买入方向 100, # 下单数量A 股买入必须是 100 的整数倍 FIX_PRICE, # 价格类型限价 10.50, # 委托价格 my_strategy_v1, # 策略名方便在客户端核对归属 first_auto_order # 委托备注用于事后审计 ) print(委托编号, order_id)这段代码执行后委托会进入 QMT 的交易通道但 order_id 只代表“委托请求已受理”不代表已经成交。成交与否要看回调这是后面第 4 章的重点。参数表格如下参数说明常见错误account已订阅的资金账号对象忘了 subscribe导致后续无回报stock_code带交易所后缀的证券代码写成 600000 不带 .SH直接废单order_typeSTOCK_BUY 买入 / STOCK_SELL 卖出方向写反酿成事故order_volume委托数量买入不是 100 的整数倍被拒price_type价格类型FIX_PRICE 为限价市价单还传限价逻辑导致价格约束失效price委托价格限价单不填价格被当成 0strategy_name策略名客户端可检索随意填充事后无法追溯order_remark备注建议写日期和批次信息3.2 价格类型的选择限价、市价与最优五档QMT 的价格类型不是只有限价和市价两种中间还有若干档。用哪个取决于你的策略对成交确定性和价格敏感度的偏好。限价单FIX_PRICE适合对入场价位有明确要求的场景比如挂单等回调。它的代价是价格偏离后可能一直不成交信号出现了但仓位没建上所以程序里要设置“未成交超时后撤单”的逻辑。市价单适合流动性好的活跃标的追求立刻成交。但市价单在盘口极薄时可能以很不利的价格成交滑点会吃掉策略收益。另一个常见选择是“对手方最优价”介于前两者之间既不会无限追价也不至于挂太久。具体常量名在券商提供的 xtconstant.py 里有定义不同版本有细微差异头一次使用先把这个文件翻一遍别凭记忆写。3.3 买入、卖出与可用持仓的读取下单之后怎么确认仓位真的变了在回调之外也可以主动查询持仓做一个核对。QMT 的持仓接口返回一个列表每个标的一条记录positions xt_trader.query_stock_positions(account) for pos in positions: if pos.stock_code 600000.SH: print(可用数量, pos.can_use_volume) print(持仓总量, pos.volume) print(成本价, pos.avg_price)这里最容易混用的是can_use_volume和volume。volume是持仓总量can_use_volume是当前可卖数量。A 股是 T1 制度当天买入的股票会出现在 volume 里但 can_use_volume 不会增加必须到下一交易日才能卖出。写卖出逻辑时一定要用 can_use_volume否则会报“可用数量不足”。卖出示例if pos.can_use_volume 100: xt_trader.order_stock( account, 600000.SH, STOCK_SELL, 100, FIX_PRICE, 10.80, sell_part, daily_rebalance )4. 把自动化做成可落地的订单流程回调、状态机与基础风控能下出第一笔单只代表“手动点按钮”被代码替代了。要让程序真正循环跑起来订单状态跟踪、失败处理、风控边界都要跟上。这一章给出一套最小但完整的订单流程。4.1 用回调接收委托状态和成交回报别只靠轮询第一次写 QMT 的人很容易在下单后写一个循环去查委托状态。这在低频场景下还行但高频或隔夜挂单场景下轮询既浪费资源又会延迟反应。更标准的做法是继承 XtQuantTraderCallback在回调里接收状态推送。from xtquant.xttrader import XtQuantTraderCallback class OrderCallback(XtQuantTraderCallback): def on_order_status(self, order): print(委托状态变化, order.stock_code, order.order_id, order.order_status) # 这里把状态写入本地 map供策略主循环判断 def on_trade_status(self, trade): print(成交回报, trade.stock_code, trade.traded_price, trade.traded_volume) # 成交后的处理逻辑写在这里比如更新持仓缓存 callback OrderCallback() xt_trader.register_callback(callback)逻辑说明回调运行在 xt_trader 的后台线程里不要在回调内部做耗时操作比如请求网络或写大文件。正确的做法是把回报里的关键信息存到一个队列或字典主线程按自己的节奏消费。参数说明order.order_status是一个整数枚举不同券商版本里具体数值可能有差异。联调第一天把收到的原始状态值打印出来和安装包里的常量文件对照一遍形成自己的映射表不要靠网上的旧帖直接猜。4.2 最小委托状态机从待报到废单任何一笔委托都会经历几个阶段不要在代码里只判断“成功”或“失败”两种状态。至少要把状态分成四类在途、部分成交、终态、异常。一个最小状态机可以用字典维护from collections import defaultdict order_map defaultdict(dict) def handle_order_status(order): order_map[order.order_id][status] order.order_status order_map[order.order_id][stock_code] order.stock_code order_map[order.order_id][volume] order.order_volume def is_final_status(order_id): status order_map[order_id][status] # 全成、已撤、废单都算终态终态之后不再处理这笔委托 return status in (FINAL_STATUS_SET)这里 FINAL_STATUS_SET 需要你在联调时从常量文件里确认。一个实际建议每天收盘后把 order_map 清空并保存当日委托记录到本地文件第二天启动时先查询昨日委托的最终状态再恢复内存状态。否则程序跑久了内存里积累的过期状态会让你误判当前持仓。4.3 基础风控下单前的检查不给循环留失控单自动化代码最常见的翻车是循环里同一信号被重复下单或者行情更新后不理智地连续追单。风控必须放在“策略下单”和“通道下单”之间单独成一个模块不跟策略混在一起。class RiskControl: def __init__(self): self.today_order_count 0 self.max_order_count 20 # 单日最大委托次数 self.max_amount 50000 # 单笔最大金额 self.stop_loss_price 0.0 # 止损保护价 def check(self, price, volume, direction): if self.today_order_count self.max_order_count: print(拒绝超过当日最大委托次数) return False amount price * volume if amount self.max_amount: print(拒绝单笔金额超限) return False if direction STOCK_SELL and self.stop_loss_price 0 and price self.stop_loss_price: print(拒绝触发止损保护) return False return True def commit(self): self.today_order_count 1这个类的核心思想是把“能不能下单”和“怎么下单”分开。每次下单前调用 check通过后才调用 commit 累计计数。today_order_count这个计数最好在程序启动时从本地文件恢复否则盘中重启一次计数归零风控相当于失效。永远不要在策略代码里写“我觉得不会重复下单”这样的假设。实际经验是进程卡顿、回调延迟、人工干预都会让同一笔委托在不知情的情况下重发。风控模块就是给这种意外兜底的。5. 上线前避坑QMT 自动化下单常见问题与排查顺序把常见问题按“现象 → 原因 → 解决”写在这里。每一条都是我见过或踩过的问题顺序不代表重要性但每一条上线前都值得对一遍。5.1 能连接但下单后收不到成交回报现象order_stock 返回了委托编号程序也没报错但回调里没有输出客户端里也看不到这笔委托。原因最常见的有三个。一是没有 subscribe 资金账号委托回报没有分发到当前进程二是回调对象没有注册事件被丢弃三是 QMT 客户端顶部的“自动交易”开关没有打开委托根本进不了交易通道。解决按链路顺序检查客户端里确认交易账号已登录且自动交易开关已打开代码里确认 subscribe 返回 0确认回调实例已经传给 register_callback。之后重新发一笔小额委托观察回调是否打印。建议把这三个检查点写进程序启动日志里方便下次直接看。5.2 本地文件读不了策略配置加载失败现象策略运行时读取一个 JSON 配置文件返回空内容或直接报权限异常。原因如果代码放在 QMT 客户端内嵌的托管策略里运行它被隔离在客户端环境中对本地文件路径的访问受到限制“国金 QMT 是否允许读本地文件”这类问题经常出现在这种模式下。本地 Python 模式则基本不受影响。解决开发阶段务必用本地 Python 模式本地文件读写完全正常。如果上线时确实需要用托管策略模式把配置文件放到策略自带的默认工作目录下或者把参数打包进策略代码里避免依赖外部路径。5.3 订单反复废单代码格式与整手约束现象日志里反复出现“废单”“无效证券代码”“非法参数”等记录。原因大部分是基础参数问题。股票代码没带 .SH/.SZ 后缀买入数量不是 100 的整数倍卖出数量超过可用持仓限价单价格填了 0。解决在下单入口统一做一层检查和规范化不要把责任留给每个调用方。代码格式规范化可以写成这样def normalize_code(code): code code.upper().strip() if code.endswith(.SH) or code.endswith(.SZ): return code if code.startswith(6): return code .SH if code.startswith(0) or code.startswith(3): return code .SZ return 这段函数的逻辑很简单已经带后缀的直接返回A 股沪市股票代码以 6 开头深市以 0 或 3 开头。调用方拿到空字符串就说明代码格式不合法拒绝下单。加上这个入口废单率能降一大半。5.4 程序重启后重复发单现象程序断电或崩溃重启后刚才已经发出的买单又发了一遍同一标的重复成交。原因下单记录只存在内存里重启后全部丢失程序以为自己还没发过这笔委托。解决每笔委托发出时把策略名、order_id、证券代码、委托价格、数量落一条到本地日志文件。程序启动时读取当日记录再调用查询接口确认这些委托的最终状态。如果本地已经存在同策略、同代码、同方向的在途委托就不再重复下单。5.5 模拟盘与实盘成交差异大现象模拟盘总是成交顺利实盘限价单长期挂在那里不成交。原因模拟盘的流动性、对手行为和撮合规则与实盘不完全一致。限价单的价格设置偏离当前盘口价格到达时又被瞬间量越过导致一直没有成交回报。解决上线实盘后先降低单笔规模并且在下单前读一次最新盘口按盘口中间价或对手价动态生成限价而不是用昨天的收盘价。同时记录“策略提交价”和“最终成交价”做一段时间的滑点统计再决定是否放大资金。6. 进阶技巧统一下单入口与模拟转实盘的验证习惯把前面几章的内容收拢成一个可复用的下单函数再讲我从模拟盘切实盘时一定会做的三笔验证。6.1 统一下单入口校验、风控、状态登记一次完成常见做法是封装一个 send_order 函数把代码规范化、风控检查、状态登记全部放在里面避免各个调用方各自写一套def send_order(xt_trader, account, raw_code, direction, volume, priceNone, tag): code normalize_code(raw_code) if not code: print(非法代码, raw_code) return None if not risk.check(price or 0, volume, direction): return None price_type FIX_PRICE if price else LATEST_PRICE order_id xt_trader.order_stock( account, code, direction, volume, price_type, price or 0, tag, tag ) if order_id: order_map[order_id] { code: code, direction: direction, volume: volume, price: price } risk.commit() return order_id这个函数的优点是所有校验集中在一处策略代码里只负责表达交易意图不用关心格式、风控、状态记录这些底层细节。加日志也方便统一在 send_order 入口打一条完整记录。6.2 模拟转实盘前我会做的三笔验证第一笔用小金额自动买入 100 股等待回调核对成交价、成交量和客户端记录一致。第二笔把其中一部分卖出确认 can_use_volume 在卖出后正确更新。第三笔手工在客户端撤掉一笔在途限价单确认回调中收到已撤状态并且内存里的 order_map 也跟着更新。这三笔验证都在小仓位下进行连续跑两到三个交易日记录废单数和成交延迟。如果这三笔全部通过再把资金逐步放大任何一笔对不上账都不要继续加仓。6.3 一点个人习惯我自己的习惯是把 QMT 自动化下单当作“水电煤”一类的基础设施来看待不给它加戏。所有状态判断都以“客户端查询 本地日志”为准不依赖内存里的变量。刚开始做这套代码时我也因为忽略 subscribe 而浪费了一整晚那种回报收不到、又不知道去哪查日志的体验经历过一次就长记性了。后来每次改动代码我都会先重跑最小连通性验证再跑模拟盘三笔验证最后才碰实盘。这套流程看起来很笨但能让自动化交易长期稳定地跑下去。希望帮到你。本文还有配套的精品资源点击获取