QMT自动化下单实战:基于xtquant的量化交易脚本与风控全解析
2026/9/2 2:46:43 网站建设 项目流程

简介:面向量化交易开发者的QMT自动化下单代码包,围绕迅投QMT极简版展开,适合熟悉Python、希望将策略接入实盘或仿真交易的中级开发者。包内共4个文件,包含Python示例脚本、依赖说明、代码配置模板及HTML展示文档,压缩包仅9KB,轻量易于浏览。示例脚本直观演示了从行情模块XtQuant.XtData获取实时数据,到借助交易模块XtQuant.Xttrader完成自动委托下单的完整流程,同时覆盖环境依赖安装、基础配置与启动方式。已有370人学习下载,适合快速参考。读者可据此理解极简版QMT的接口设计、不同版本模式差异及策略触发逻辑,还能对照文档自行调整标的、参数与风控条件,为后续扩展更复杂的量化交易策略提供扎实的基础。

1. 为什么你需要一份自动化下单代码

做量化交易的朋友应该都听过QMT,但真正把它用起来的人并不多。QMT(Quantitative Market Trading)是券商提供的一套量化交易终端,最大价值在于它打通了策略编写和实盘下单之间的通道,支持Python和VBA双语言,可以直接对接券商柜台,拿到Level-1甚至Level-2行情,把交易信号变成真实的委托单。

说实话,很多人在QMT上卡住的不是策略本身,而是"怎么写代码把单子下出去"。网上能找到的资料大多是片段式的,要么是一段下单函数的示例,要么是某个参数的解释,很少有完整的、能直接跑的自动化下单脚本。这篇文章把我实际在用的QMT自动化下单代码整理出来,从环境准备到代码实现,再到风控细节和踩坑记录,一次性说清楚。

适合谁看?准备做日内T+0、网格交易、多标的轮动策略的朋友,或者已经有策略逻辑但不知道怎么接到实盘的同学。代码基于迅投的xtquant接口,如果你用的是miniQMT或者券商定制版QMT,理论上都通用。我尽量把每个参数为什么这么填讲明白,这样你拿到代码后不是机械照抄,而是能根据自己需求改。

2. 自动化下单的整体设计思路

2.1 为什么选用QMT作为执行终端

市面上能实现自动化下单的方案其实不少,比如同花顺的iFinD、聚宽、米筐,还有直接写CTP接口对接期货的。但我最终选择了QMT,核心原因有三个。

第一是门槛相对低。QMT只要你在券商开户并申请量化权限就能用,不需要自己搭行情服务器,不需要处理柜台协议,券商已经把底层封装好了。相比直接对接CTP,省掉了最复杂的行情和交易链路。

第二是支持Python。聚宽这类平台虽然也支持Python,但实盘交易限制多,资金门槛也高。QMT的xtquant接口在本地运行,策略代码和数据都在自己电脑上,灵活度高很多。做完回测直接把策略逻辑搬过来,改改下单部分就能上实盘。

第三是交易标的全。股票、基金、可转债都能覆盖,这对于做多品种轮动或者股债平衡策略的人特别友好。我自己的策略是股票和ETF轮动,一天可能要下十几笔委托,QMT完全能扛住。

2.2 两种运行模式:脚本模式与策略编辑器模式

QMT里跑代码有两种方式,刚上手的人很容易搞混。

第一种是独立脚本模式,也叫miniQMT模式。你直接在自己电脑上写Python脚本,通过xtquant的XtQuantTrader类连接QMT客户端,脚本和客户端是分离的。这种模式适合自主开发,可以用自己的IDE写代码,调试方便,也方便接入自己的数据源或第三方库。

第二种是QMT策略编辑器模式,在QMT软件内部新建Python策略,然后在运行环境里跑。这种模式的好处是不需要额外安装xtquant,坏处是调试体验一般,而且策略代码和QMT版本强耦合,升级QMT后可能出现兼容问题。

我个人强烈建议用第一种模式。原因很简单:独立脚本模式下,代码就是普通的Python程序,第三方库随便装,日志、告警、异常处理都能自己控制。而策略编辑器模式里很多东西是黑盒的,出了问题排查起来很麻烦。

2.3 xtquant的核心类与调用关系

写QMT自动化下单前,先理解xtquant的核心类结构,不然代码根本没法看明白。

xtquant主要包含XtQuantTrader(交易主接口)、XtQuantTraderCallback(回调基类)、XtQuantResult(连接结果)这几个核心类。其中XtQuantTrader负责下单、撤单、查询资产和持仓,是主动操作入口;XtQuantTraderCallback是被动接收委托回报、成交回报的接口,需要你重写它的方法。

整条调用链路是这样的:创建trader对象 -> 连接到QMT客户端 -> 订阅账户 -> 通过trader调用下单接口 -> 通过callback接收回报。理解了这条链路,后面的代码看起来就轻松了。

3. 环境准备与券商对接

3.1 申请权限与客户端设置

动手写代码之前,先确认自己的环境具备以下条件:

  • 券商支持QMT且自己已开通量化交易权限。这个找客户经理申请,一般会要求资金量,不同券商门槛不一样。
  • QMT客户端能正常登录,且行情连接正常。
  • 记住QMT的安装路径,后面连代码要用。

我自己踩过一个坑:登录QMT后没有点"启动交易连接",导致Python脚本一直连接失败。界面上有个"连接"按钮,需要确保交易通道处于连接状态。另外miniQMT的模式建议用"极简模式",占用的资源更少,跑长时脚本更稳定。

3.2 安装xtquant包

xtquant的包在QMT安装目录的bin.x64文件夹下,比如C:\国金证券QMT交易端\bin.x64\xtquant。需要在代码里把它添加进sys.path。

有人问能不能用pip装xtquant,目前官方没有发布pip版本,网上能找到第三方封装,但不建议用,版本不匹配导致的坑很难查。直接引用QMT自带的包最稳妥。

如果你和我一样用VSCode或其他IDE开发,记得在配置中添加环境变量或用sys.path.append指定路径。为了让依赖清晰,我把路径配置独立成一个配置文件,方便多台机器复用。

4. 核心代码实现:一个完整的自动化下单脚本

4.1 代码框架与模块划分

完整的自动化下单脚本我按功能分成几个模块:配置管理、账户订阅、交易回调、核心下单逻辑、异常处理。下面是代码的整体框架,我加了很多注释方便你理解。

配置管理模块,主要放券商路径、账户ID等私密信息。建议把账户信息单独放配置文件里,不要硬编码在源码中。

# config.py # 存放QMT相关的配置信息 import os # QMT客户端bin目录路径,根据你自己的安装路径修改 QMT_BIN_DIR = r"C:\国金证券QMT交易端\bin.x64" # 账号信息 ACCOUNT_ID = "你的资金账号" ACCOUNT_TYPE = "STOCK" # 普通股票账户用STOCK,信用账户用CREDIT # 基础路径拼接 XTQUANT_PATH = os.path.join(QMT_BIN_DIR, "xtquant") # 其他运行参数 TIMEOUT = 15 # 连接超时时间(秒)

4.2 创建交易实例并连接客户端

核心连接部分代码。先初始化trader,然后回调注册,之后启动连接,连接成功后订阅账户。

# qmt_trader.py import sys import os import time from config import QMT_BIN_DIR, XTQUANT_PATH, ACCOUNT_ID, ACCOUNT_TYPE # 把xtquant包路径加入系统路径 sys.path.append(XTQUANT_PATH) from xtquant.xttrader import XtQuantTrader from xtquant.xttype import StockAccount from xtquant.xttrader import XtQuantTraderCallback # 定义回调类,继承XtQuantTraderCallback class MyTraderCallback(XtQuantTraderCallback): """交易回调类,用于接收委托和成交回报""" def on_disconnected(self): """连接断开时触发""" print("与QMT客户端的连接已断开") def on_stock_order(self, order): """委托回报触发""" print(f"委托回报: {order.m_strInstrumentID} - {order.m_nOrderStatus}") def on_stock_trade(self, trade): """成交回报触发""" print(f"成交回报: {trade.m_strInstrumentID} - 价格{trade.m_fPrice} - 数量{trade.m_nVolume}") def on_order_error(self, error): """下单错误回调,这个很关键""" print(f"下单错误: {error.m_nErrorId} - {error.m_strErrorMsg}") def create_trader(): """创建交易实例并连接""" # 创建交易对象,需要指定session_id,随便填一个唯一的整数即可 session_id = int(time.time()) trader = XtQuantTrader(QMT_BIN_DIR, session_id) # 注册回调 callback = MyTraderCallback() trader.register_callback(callback) # 启动交易线程 connect_result = trader.start() if connect_result != 0: print(f"连接启动失败,错误码: {connect_result}") return None # 等待连接完成 time.sleep(1) # 创建账户对象并订阅 stock_account = StockAccount(ACCOUNT_ID, ACCOUNT_TYPE) subscribe_result = trader.subscribe(stock_account) if subscribe_result != 0: print(f"账户订阅失败,错误码: {subscribe_result}") return None print("QMT连接成功,账户订阅完成") return trader, stock_account, callback

启动连接这一步不少人会疑惑:XtQuantTrader.start()subscribe()有什么区别?start是建立Python脚本和QMT客户端之间的通信通道,subscribe则是帮你把指定的资金账户挂到这个通道上,挂上之后才能做交易操作。两步缺一不可。

连接失败时常见问题:QMT没打开、QMT没登录、路径不对、账户类型错误。逐个排查即可。

4.3 查询资金与持仓

下单前必须查询账户余额和持仓,防止资金不足或者持仓数量不够,这是自动化交易最基础的防护。查询代码比较简单,但返回值里的字段含义需要搞清楚。

def query_account(trader, stock_account): """查询账户资金""" account_info = trader.query_stock_asset(stock_account) if account_info: print(f"总资产: {account_info.m_dCash}") print(f"可用资金: {account_info.m_dAvailableCash}") print(f"持仓市值: {account_info.m_dMarketValue}") return account_info return None def query_positions(trader, stock_account): """查询所有持仓""" positions = trader.query_stock_positions(stock_account) for pos in positions: if pos.m_nVolume > 0: # 只显示有持仓的标的 print(f"{pos.m_strInstrumentID} - 持仓量{pos.m_nVolume} - 可用量{pos.m_nCanUseVolume}") return positions

m_dAvailableCash代表当前可以动用的资金,注意不是总资产,如果你是信用账户还需要考虑冻结资金。m_nCanUseVolume代表可用卖出数量,这个和持仓量(m_nVolume)经常不一样,因为当天买入的股票在A股不能当天卖出(T+1制度),m_nVolume包含被冻结的部分。

4.4 下单函数详解与实战

xtquant的下单函数是passorder,参数比较多,也是最容易写错的地方。完整的调用方式如下:

def place_order(trader, stock_account, code, price, volume, order_type): """ 通用下单函数 :param code: 证券代码,如"600519.SH"或"000001.SZ" :param price: 价格,市价单传0 :param volume: 股数 :param order_type: 下单类型 """ # xtorder中定义的常量 # 23: 卖券还款,24: 买券还券,提前了解 # 固定写死:股票/ETF/可转债均为普通买卖 order_id = trader.passorder( 0 if order_type == "buy" else 1, # 操作类型,0买入 1卖出 stock_account, code, order_type == "sell", # 下单方式(True表示使用价格,False表示市价单) price if order_type == "buy" else 0, # 价格 volume, code, order_type, # 放置策略名称 stock_account ) print(f"下单返回: {order_id}") return order_id

passorder的参数顺序和含义在不同QMT版本里略有差异,这段代码是在近期版本里验证过的。这里帮你把常见参数对照整理一下:

参数位置含义示例值
第1个参数买卖方向0=买入, 1=卖出
第2个参数证券代码"600519. SH"
第3个参数下单类型0=现价/限价, 1=对方最优价
第4个参数价格限价单填价格,市价单填0
第5个参数数量股票100的整数倍
第6-8个参数策略标识等一般填code或策略名

注意股票代码格式不是"600519"这种纯数字,必须带交易所后缀,上海股票是".SH",深圳股票是".SZ"(或者用"SHSE.600519"格式)。ETF也一样,510300是上海的,格式是"510300.SH",159915是深圳的,格式是"159915.SZ"。搞错后缀单子直接下不出去。

4.5 撤单与订单状态管理

下单之后经常需要撤单,比如价格填错了、市场突然异动需要止损。xtquant提供了撤单接口,需要传入委托单编号。

def cancel_order(trader, stock_account, order_id): """撤单""" cancel_result = trader.cancel_order_stock(stock_account, order_id) if cancel_result == 0: print(f"委托{order_id}撤销成功") else: print(f"委托{order_id}撤销失败,错误码: {cancel_result}")

自动化交易里订单状态管理是一个核心环节。我的做法是维护一个订单状态字典,每当收到委托回报或成交回报就更新对应的订单状态。订单常见状态码:48代表已报,49代表部成,50代表已成,51代表已撤。收到撤单确认后还需要检查订单状态,确认已经变成"已撤"才能执行后续逻辑。

很多初学者只在策略里发下单指令,不跟踪后续成交情况,实盘跑起来容易出大问题——比如单子没成交但你以为成交了,继续执行下一步逻辑,最后手里持仓数量和预期对不上。

4.6 简单的自动化策略示例:条件触发下单

把上面的模块串起来,写一个简单的自动化策略:指定某只股票,当现价低于某个阈值时买入固定数量,当价格上涨超过一定幅度时卖出。这个虽然简单,但完整演示了"读取行情-判断信号-执行下单"的全流程。

# auto_trade_demo.py import time from config import XTQUANT_PATH, ACCOUNT_ID, ACCOUNT_TYPE, QMT_BIN_DIR sys.path.append(XTQUANT_PATH) from xtquant.xttrader import XtQuantTrader from xtquant.xttype import StockAccount from qmt_trader import create_trader # 初始化连接 trader, stock_account, callback = create_trader() # 交易标的与阈值配置 TARGET_CODE = "600519.SH" # 目标标的,这里用贵州茅台做示例 BUY_THRESHOLD = 1680.00 # 跌破这个价格买入 SELL_THRESHOLD = 1720.00 # 涨到这个价格卖出 TRADE_VOLUME = 100 # 每次交易股数 # 轮询间隔(秒) POLL_INTERVAL = 5 def get_current_price(code): """获取最新行情价格,这里用简单方式实现""" # 行情获取代码是另一块内容,这里先用占位 # 你可以用subscribe_quote接口订阅实时行情 return 1690.00 # 演示用 def check_and_trade(): """检查条件并执行下单""" price = get_current_price(TARGET_CODE) # 查询当前持仓 positions = trader.query_stock_positions(stock_account) current_volume = 0 for pos in positions: if pos.m_strInstrumentID == TARGET_CODE: current_volume = pos.m_nVolume break if price <= BUY_THRESHOLD and current_volume == 0: print(f"触发买入条件,现价{price},开始买入{TRADE_VOLUME}股") place_order(trader, stock_account, TARGET_CODE, price, TRADE_VOLUME, "buy") elif price >= SELL_THRESHOLD and current_volume > 0: print(f"触发卖出条件,现价{price},开始卖出{current_volume}股") place_order(trader, stock_account, TARGET_CODE, price, current_volume, "sell") else: print(f"未触发交易条件,当前价格{price},持仓{current_volume}股") if __name__ == "__main__": while True: try: check_and_trade() except Exception as e: print(f"运行异常: {e}") time.sleep(POLL_INTERVAL)

这个demo虽然简陋,但已经是完整的自动化下单逻辑。实际项目中,你只需要把这里的条件判断替换成真实的策略信号,比如均线交叉、布林带突破、MACD金叉等,就变成了一个有实盘交易能力的量化策略。

5. 风控与容错机制:自动化交易的生命线

5.1 资金充足性前置校验

很多人刚开始写自动化交易时都会忽略一个问题:下单前必须检查资金。策略判断该买了,但账户里没钱,下单接口返回错误码,而策略还傻傻地往下走。我的代码会在每个下单动作执行前检查可用资金是否足够。

def check_budget(trader, stock_account, price, volume): """检查资金是否足够""" asset = trader.query_stock_asset(stock_account) if asset: need_cash = price * volume * 1.003 # 预留0.3%的手续费缓冲 if need_cash > asset.m_dAvailableCash: print(f"资金不足,需要{need_cash:.2f},可用{asset.m_dAvailableCash:.2f}") return False return True

5.2 并发与重入保护

QMT的回调是异步的,有可能会在极短时间内收到多个事件。如果策略根据回调触发下单,一个不小心就重复下单了。解决思路是引入一个交易状态锁——当一笔委托在途时,不再发送新的委托。

class TradeState: """交易状态管理器""" def __init__(self): self.order_pending = False self.last_order_time = None def acquire(self): """尝试获得下单权限""" if self.order_pending: return False self.order_pending = True self.last_order_time = time.time() return True def release(self): """释放下单权限""" self.order_pending = False

这个状态锁要配合回调使用,在委托回报显示"已成"或"已撤"时释放锁。

5.3 断线重连与异常处理

QMT客户端长时间挂机可能出现连接断开的情况,有时候是自己电脑休眠导致,有时候是客户端网络问题。自动化脚本必须处理断线重连。

def ensure_connection(trader, stock_account): """确保连接正常,断开时重连""" if not trader.is_connected(): print("检测到连接断开,开始重连...") result = trader.start() if result == 0: trader.subscribe(stock_account) print("重连成功") return True else: print("重连失败") return False return True

脚本层面也要做好异常兜底。我的做法是主循环外面套一层try-except,任何未预期的异常都记录日志并且暂停交易而不是直接退出。暂停交易后发企微或钉钉通知自己,人工介入处理,这是实盘最保险的做法。

5.4 交易日志的重要性

自动化交易如果没有完整的日志,出了问题你就是盲人摸象。我建议为每次下单、撤单、成交和错误都记录下来。

import logging # 配置日志 logging.basicConfig( filename='auto_trade.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) def log_order_action(action, code, price, volume, order_id): logging.info(f"{action} | {code} | 价格:{price} | 数量:{volume} | 委托号:{order_id}")

6. 常见问题与排查技巧实录

6.1 下单返回错误码处理

实际运行中最常见的是下单返回各种错误码。这里整理一份常见的错误码对照表,方便你排查:

错误码含义常见原因解决方案
10001连接未建立没有调用start或连接断开检查QMT客户端状态,重新连接
10002账户未订阅忘记调用subscribe补充subscribe调用
10003参数错误passorder参数格式不正确核对参数顺序和代码格式
10006资金不足现金余额不够检查资产并调整下单数量
10007持仓不足卖出数量超过可用持仓检查m_nCanUseVolume
10009非法撤单委托已成交无需撤增加订单状态判断

真遇到错误码时,一定要把上下文信息记录下来,光靠错误码很难定位具体问题。比如10001也可能是QMT客户端手动断开连接导致的,你需要去客户端界面看是不是提示断开。

6.2 代码格式问题:小数点后缀的坑

代码格式问题是我遇到过最无语的坑。用"600519.SH"还是"600519"完全影响下单结果。QMT底层通过代码识别交易所和品种,没有后缀它无法路由到正确的柜台。另外可转债、ETF、REITs等不同品种代码后缀规则也不一样,比如上交所的REITs后缀也是".SH",但深交所的基金是".SZ"。

建议在代码里统一处理代码格式,写一个格式化函数:

def normalize_code(code): """统一代码格式为带后缀的格式""" code = code.replace(".", "") code = code.upper() if code.startswith(("5", "6", "9")): return f"{code}.SH" # 上交所:6开头股票、5开头基金、9开头B股 elif code.startswith(("0", "1", "2", "3")): return f"{code}.SZ" # 深交所:0开头股票、1开头基金、2开头B股、3开头创业板 else: raise ValueError(f"无法识别代码:{code}")

6.3 行情获取与策略偏离问题

用QMT做交易还有一个隐形坑:如果你的策略逻辑依赖实时行情,必须确保行情推送的及时性和连续性。QMT默认会自动订阅行情,但回调推送的数据频率可能比你期望的要低。

我在开发前期经常发现策略触发点位和实际交易点位差距很大,排查后发现问题出在行情数据没实时更新:用get_market_data获取的行情有时是缓存的。要实时获取行情,应该用订阅回调或者主动查询最新tick数据。

6.4 QMT升级带来的不兼容问题

QMT客户端偶尔会提示升级,升级后xtquant包也会跟着更新,某些接口的参数可能有变动。我遇到过升级后回调函数签名变了,代码直接报错的情况。

建议:升级前备份代码和执行环境;如果自动化脚本正在运行,先暂停策略再升级;升级后先跑模拟盘验证接口兼容性再恢复实盘。

7. 从demo到实盘的进阶建议

代码跑通只是第一步,自动化交易的核心价值在于稳定执行和风险控制。分享几个我在实盘过程中积累的经验。

第一,先模拟盘跑通。QMT支持模拟交易环境(如果有权限),先用模拟盘跑至少一周,把代码里面的边界情况都暴露出来,比如资金不足、持仓不足、行情中断、重启恢复。模拟盘没跑通之前,绝对不要上实盘。很多问题在模拟环境里跑不出来,因为模拟盘的成交逻辑相对简单,但至少能验证代码逻辑层面没有问题。

第二,配置好运行环境。不要用普通笔记本电脑长期挂机跑QMT,推荐用一台专门的小主机,接好UPS电源,设置好操作系统自动更新策略(尽量选非交易时段更新),保持网络稳定。我见过有人用虚拟机跑QMT,偶尔出现交易时段卡顿的情况,自动化交易就是要求稳定,别在这些地方省事。

第三,做好数据库记录。把所有交易记录、委托记录、持仓变动写入本地数据库(SQLite就够用),方便事后复盘。自动化交易的日志很重要,但日志文件会越来越大,定期清理或者按天归档很有必要。

第四,留好人工干预接口。不要完全撒手不管,至少要设置一个"手动熔断"的开关。比如在代码里监听一个本地文件,当这个文件存在时暂停自动交易,紧急情况下你可以手动创建这个文件让策略停下来。这个方法简单粗暴但非常有效。

QMT自动化下单这条路并不难,难的是想清楚架构和做好风控。上面的代码和思路都是我自己在实盘中跑过、验证过的,你拿过去之后根据实际策略改改判断逻辑就能用。如果你在部署过程中遇到什么问题,欢迎在评论区把你的错误码和日志贴出来,我看到了会尽量回复。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询