ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

miniQMT实盘交易系统开发:订单管理、风控与代码骨架详解

2026/9/17 10:27:44 拓冰建站 浏览量
miniQMT实盘交易系统开发:订单管理、风控与代码骨架详解 拿我自己做量化这几年的经验来说从策略回测到真正把资金放进去跑实盘中间隔的最大的一个坎就是交易执行。回测的时候点一下run等结果就行实盘完全不是一回事一笔单子撒出去你要知道它报没报进去、有没有部分成交、要不要撤单重挂、可用资金够不够、盘中断线了怎么办这一整套东西就是交易系统。miniQMT的xtquant接口帮你把下单通道打通了但“能用接口下单”和“能稳定跑实盘”之间还差着一层非常关键的工程化封装。这篇文章是miniQMT实盘量化执行程序系列的第三篇前两篇偏重环境准备、数据获取和策略信号计算这一篇专门讲交易执行层。我会直接从我常用的一个交易系统骨架出发把通道初始化、订单生命周期管理、资金持仓管理、风控检查、仓位计算、断线重建这些环节逐个拆开讲最后给出一份可以直接抄走改改就用的代码框架。想上实盘但又对交易执行这块没底的朋友这篇应该能帮你少踩不少坑。1. 从回测到实盘交易系统到底要解决什么问题很多刚开始接触miniQMT的朋友会有一个误区觉得策略信号算出来了调用一下下单接口交易系统就完事了。实际真不是这样。回测里你只需要关心“这个时点该买还是该卖”实盘里你要关心的是“这笔单子从发出去到最终成交中间经历了哪些状态、每个状态是否正常、如果不正常该怎么处理”。我习惯把实盘交易系统拆成五个层通道层、订单管理层、资金层、风控层、告警层。通道层负责和QMT客户端建立连接、维持心跳、处理断线重连订单管理层负责下单、撤单、查询委托和成交、维护订单的本地状态资金层负责查询账户资产、可用资金、持仓并提供仓位计算能力风控层负责在下单前做价格、数量、资金、涨跌停等合规检查告警层负责把关键事件推送出来比如下单失败、断线、成交回报超时等。五个层各管一摊互不干扰出问题的时候排查起来非常快。还有一种情况需要提前想清楚你到底跑全自动还是半自动。全自动是指策略信号、风控、下单、撤单全部由程序完成盘中不需要人工介入半自动则是策略负责出信号程序把单子挂到界面或者推送给你由你人工确认后再下单。从我个人的经验看第一天上实盘的时候最好先跑半自动跑个一两周把系统稳定性验证完再逐步放开成全自动。这个过渡非常重要因为实盘环境下的问题往往不是策略问题而是系统边界问题。2. 交易通道封装与初始化2.1 XtQuantTrader的初始化连接miniQMT的下单核心类是XtQuantTrader它不是一个单例而是在你的程序里独立创建的一个实例。初始化的时候需要两个参数一个是QMT客户端的userdata路径另一个是session_id。from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback from xtquant.xttype import StockAccount import time class TradeEngine: def __init__(self, path, session_id, account_id): self.path path self.session_id session_id self.account_id account_id self.trader None self.account StockAccount(account_id) self._connected False def start(self): self.trader XtQuantTrader(self.path, self.session_id) self.trader.register_callback(TradeCallback(self)) self.trader.start() result self.trader.connect() if result 0: self.account StockAccount(self.account_id, STOCK) self.trader.subscribe(self.account) self._connected True print(f[交易通道] 连接成功账户{self.account_id}) else: self._connected False print(f[交易通道] 连接失败错误码{result}) return self._connected这里有一个非常关键的细节session_id必须是一个唯一值不能跟QMT客户端里其他正在运行的miniQMT进程用同一个id。XtQuantTrader在连接QMT时是通过session_id来区分不同的连接来源的如果你同时跑多个策略进程session_id冲突会导致其中一个连不上或者互相顶掉。我一开始没注意这个问题结果两个策略文件共用了一个session_id实盘时经常出现一个策略刚连接、另一个策略就被迫断开的情况。后来我改成用时间戳或者进程号动态生成session_id问题就再没出现过。connect()返回0代表连接成功非0错误码需要到xtquant的文档里对照排查。连接成功后记得调用subscribe(account)订阅账户不订阅的话委托、成交这类的异步回调是收不到的。这个坑也很隐蔽很多人连接成功了但回调一直不触发排查半天发现是忘了subscribe。2.2 回调机制与线程模型XtQuantTraderCallback是交易系统接收异步回报的入口下单之后QMT端的回报会通过回调推送给你。常用的回调方法有四个on_disconnected()交易通道断开时触发on_stock_order(order)委托回报订单状态变化时触发on_stock_trade(trade)成交回报有成交时触发on_order_error(order_error)委托失败时触发on_cancel_error(cancel_error)撤单失败时触发class TradeCallback(XtQuantTraderCallback): def __init__(self, engine): super().__init__() self.engine engine def on_disconnected(self): self.engine._connected False print([交易通道] 连接断开准备重连...) self.engine.reconnect() def on_stock_order(self, order): print(f[委托回报] {order.stock_code} 订单号{order.order_id} 状态码{order.order_status} 委托{order.order_volume}股 价格{order.price}) def on_stock_trade(self, trade): print(f[成交回报] {trade.stock_code} 订单号{trade.order_id} 成交{trade.traded_volume}股 价格{trade.traded_price}) def on_order_error(self, order_error): print(f[委托失败] 订单号{order_error.order_id} 错误码{order_error.error_code} 原因{order_error.error_msg}) def on_cancel_error(self, cancel_error): print(f[撤单失败] 订单号{cancel_error.order_id} 原因{cancel_error.error_msg})线程模型这里要特别说一句。回调不是跑在你的主线程里的xtquant的回调有自己的异步线程。这意味着你在回调里不能处理太重的逻辑比如不能在回调里直接去跑策略计算、去写数据库、去做网络请求这些操作会把回调线程卡住一旦回调线程阻塞后续的委托和成交回报都会堆积延迟。正确做法是在回调里只做轻量的状态更新把订单号、股票代码、状态这些关键信息塞进队列然后由主线程或者单独的工作线程去消费处理。我见过一个案例别人的程序在成交回调里直接去请求外部HTTP接口做通知结果接口超时了三秒这三秒里其他订单的回报全部堵住了那个时间点刚好有一笔单子需要撤单重挂因为回报延迟直接被吃了个不小的亏损。2.3 多策略多账户时的session_id管理如果你的交易系统要同时服务多个策略每个策略可能对应不同的资金账号那session_id和回调注册的管理方式也要提前设计好。我目前的做法是一个TradeEngine实例绑定一个策略、一个账户、一个独立的session_id每个引擎单独创建XtQuantTrader实例。策略之间完全隔离一个策略崩了不会影响另一个策略的订单回调。代码结构上用一个引擎管理器统一维护class EngineManager: def __init__(self, path): self.path path self.engines {} self._session_counter 0 def create_engine(self, account_id): self._session_counter 1 session_id int(time.time() * 1000) % 100000 self._session_counter engine TradeEngine(self.path, session_id, account_id) engine.start() self.engines[account_id] engine return enginesession_id用“时间戳序号”拼一下基本可以保证唯一。这个方法其实也不算复杂但能省掉很多多进程连接时的奇怪问题值得一开始就做好。3. 订单生命周期的完整处理3.1 从下单到成交的四种回报一笔订单发出去之后QMT端会按阶段推送不同类型的回报。我把它们归类成四种委托回报、成交回报、委托失败回报、撤单失败回报。委托回报告诉你订单状态变了比如从“已报”变成“已撤”或者从“部分成交”变成“全部成交”成交回报告诉你某笔订单成交了多少股、成交价是多少委托失败和撤单失败则是告诉你某个操作没有成功。这里要注意的是一笔订单可能对应多笔成交回报。比如你下了一笔5000股的买单市场流动性不够可能先成交2000股再成交3000股每笔成交都会单独推送一次on_stock_trade。你在统计实际持仓变化时不能只看一次成交回报就下结论要按订单号维度去累加成交数量。还有一点成交回报里只带traded_volume和traded_price但很多时候你还需要知道这笔成交对应的是哪只股票。用trade.stock_code去取就行。在本地记录交易流水的时候我习惯把订单号、股票代码、买卖方向、成交数量、成交价格、成交时间六要素全部落库后续做盈亏分析、对账都靠它。3.2 订单状态机拆解订单在QMT里的状态是用状态码表示的。常用的几个状态码我列一下状态码含义处理建议48未报等待进入交易系统一般很快会变成已报如果长时间停留要注意49待报中间状态可忽略50已报订单已进入交易所等待成交51已报待撤撤单指令已发送等待确认52部成待撤部分成交剩余未成交部分在撤单流程中53部撤部分成交剩余部分已撤销54已撤全部撤销订单结束55部成部分成交剩余未成交部分还在挂着56已成全部成交订单结束57废单订单被交易所拒绝不会有成交状态机不复杂但处理逻辑要严谨。你想象一下订单在“已报”状态下你不想等了要撤单这时候发出撤单指令状态变成了“已报待撤”然后QMT端确认撤成最终状态变成“已撤”。如果撤单指令在交易所还没来得及处理订单又恰好全部成交了那撤单就会失败状态直接跳到“已成”。这个边界情况一定要在代码里处理不然容易出现“订单已经成交了但本地状态还记着是已撤”的脏数据。所以我在交易系统里不会去“猜”订单的最终状态而是以回调收到的最终状态为准。本地维护一个订单表每次收到委托回报就更新状态收到成交回报就累加已成交量最终状态由“订单表状态已成交量”双维度来判定。3.3 撤单与改价实现撤单接口是cancel_order_stock(account, order_id)传入账户对象和订单号即可。但撤单有个前提订单必须处于可撤状态比如“已报”或“部成”状态如果订单已经“已成”或“已撤”撤单会返回失败。在实盘里改价我很少直接用“改单”接口因为QMT本身没有直接改单的接口常规做法就是先撤单等撤单确认后再按新价格重新下单。整个流程要串联起来流程是def replace_order(self, order_id, new_price): # 第一步撤单 cancel_result self.trader.cancel_order_stock(self.account, order_id) if cancel_result ! 0: print(f[改价] 撤单失败错误码{cancel_result}) return None # 第二步等待撤单回调确认状态变成已撤或部撤 # 这里建议用事件或轮询等待不要直接sleep后盲目下单 time.sleep(1) order self.trader.query_stock_order(self.account, order_id) if order.order_status not in [53, 54]: print(f[改价] 撤单未完成当前状态{order.order_status}) return None # 第三步按新价格重新下单 new_order_id self.order_stock(order.stock_code, order.order_type, order.order_volume - order.traded_volume, order.price_type, new_price) return new_order_id这里有个细节撤单之后重挂下单数量要减去已经成交的部分。你下5000股撤单前成交了2000股那重挂的时候只能挂3000股否则就超买了。这个数量换算的逻辑必须在系统里写对。3.4 订单与本地任务表的映射在策略层你发起一个操作可能是“买入股票A 5000股”或“卖出股票B 3000股”这个操作被拆成订单号之后你要能在本地知道这个订单号对应的是哪次策略操作。我维护一个任务表每次下单前生成一个task_id下单后把系统返回的order_id和task_id绑定起来self.task_map[order_id] { task_id: task_id, stock_code: stock_code, direction: direction, target_volume: volume, traded_volume: 0, status: submitted, create_time: time.time() }回调收到委托或成交回报时通过order_id去查task_map更新对应任务的成交数量和状态。这样策略层想知道“我的买入任务完成没有”直接查task_map[order_id]就行不用自己去翻查询接口。这个映射关系在我写的多个实盘系统里都是核心的数据结构把它设计好了后续做日志、统计、告警都方便很多。4. 资金持仓管理与仓位控制4.1 资产查询与可用资金下单之前最重要的事是确认资金够不够。QMT提供了query_stock_asset(account)接口返回的是一个StockAsset对象里面包含现金、冻结资金、总资产、持仓市值等关键字段。我封装了一个查询方法把需要的字段整理成字典方便策略层直接调用def query_asset(self): asset self.trader.query_stock_asset(self.account) if asset is None: return None return { cash: asset.cash, # 可用资金 frozen_cash: asset.frozen_cash, # 冻结资金 market_value: asset.market_value, # 持仓市值 total_asset: asset.total_asset, # 总资产 position_value: asset.position_value }这里要特别注意的是cash代表的是“当前可用资金”不是“总资金”。你买入股票后资金会被冻结冻结部分在成交前是不能用来买其他股票的。如果你用总资产去算单笔下单金额大概率会因为资金不足被交易所废单。正确的下单金额上限应该以cash为基准再预留一小部分作为手续费和冲击成本余量。4.2 调仓时如何计算买卖数量仓位管理这块我分享一下我自己在用的一个等权目标仓位算法刚好能展示“计算逻辑是怎么落到实盘代码里的”。假设你有10只股票的股票池总资产100万每只股票的目标权重10%也就是每只股票目标市值10万。当前股票A的持仓市值是8万目标市值10万说明差2万需要买入。当前价按买一价算比如10元则目标加仓股数20000/102000股。但因为A股一手是100股2000股刚好是整数。如果计算出来是个非整数比如2150股就要向下取整到2100股不能直接下2150股否则会被废单。def calc_adjust_volume(self, stock_code, target_value, current_value, price, lot_size100): diff_value target_value - current_value if abs(diff_value) 1000: return 0 raw_volume int(abs(diff_value) / price) volume (raw_volume // lot_size) * lot_size if diff_value 0: return volume else: return -volume还有一个很关键的点分红送股会导致持仓数量变化。比如你持有1000股某天10送10第二天持仓就变成2000股了。你的目标市值如果还按原来的持仓股数去算调仓信号可能会有偏差。所以实盘中每次调仓前都建议重新查一次持仓再计算而不是沿用策略层缓存的持仓数据。4.3 同一标的重复调仓的保护实盘里最容易犯的错误是同一个标的同时挂了多笔单。比如策略刚发了一笔买入指令还没来得及成交下一根K线又触发了一个加仓信号结果系统又下一单最后两笔叠加导致超买。我的做法是在下单前做一个检查如果任务表里已经有同一股票同一方向的未完成订单则拒绝新下单除非有明确的强平指令。def _check_existing_task(self, stock_code, direction): for order_id, task in self.task_map.items(): if task[stock_code] stock_code and task[direction] direction: if task[status] in [submitted, partial]: return False return True实测下来这个保护非常有必要。策略层经常会出现信号闪烁或者重复触发的情况有了这一层拦截至少不会因为自己的重复下单造成仓位失控。5. 实盘风控检查清单5.1 下单前的参数硬校验风控不是单独的一个模块它应该侵入到每次下单的必经之路上。我每次在调用order_stock之前都会先跑一遍risk_check把明显有问题的单子直接拦下来不进交易通道。校验项包括价格必须大于0、数量必须是100的整数倍、数量不能超过单笔上限、股票代码不能为空、买卖方向必须合法。这些看起来是最基础的东西但实盘里最容易出幺蛾子的恰恰就是这些基础校验没做好。有个印象很深的例子某个策略用了实时行情里的最新价去下单但盘中最新价因为停牌变成0程序没判断就直接下单下单价格是0。这种单肯定会被交易所废掉但浪费了一次操作机会而且在日志里还会留一条废单记录排查起来特别烦。所以我在风控函数里把“价格必须大于前收盘价的某个比例”这种规则也加进去了。5.2 涨跌停价格判断A股有涨跌停制度主板股票涨跌幅是10%创业板和科创板是20%北交所是30%。你下买单价格不能高于涨停价下卖单价格不能低于跌停价。否则订单会被交易所判定为无效单变成废单。问题是涨跌停价怎么获取。最简单的方式是直接用xtquant的行情接口XtInstrumentDetail里有UpperLimitPrice和LowerLimitPrice字段拿到的就是当天的涨跌停价。实盘里建议每次下单前实时取一次不要缓存因为不同板块的涨跌幅比例不一样而且遇到新股上市、ST股等特殊情况涨跌幅可能会变化。def get_limit_price(self, stock_code): from xtquant.xtdata import get_instrument_detail detail get_instrument_detail(stock_code) if not detail: return None, None return detail.get(UpperLimitPrice), detail.get(LowerLimitPrice)下单价格在风控层的处理逻辑是买单价格 min(客户指定价格, 涨停价 - 0.01)卖单价格 max(客户指定价格, 跌停价 0.01)这样做的好处是即便策略层因为某些原因传了一个越界价格到交易通道之前也会被强制修正不会产生废单。5.3 100股整数倍处理A股买入必须按100股整数倍卖出可以卖出不足100股的零股但零股必须一次性卖出。这个规则看起来简单但实盘里很容易因为分红送股导致持仓出现零股这时候调仓算法如果还在按100股取整会导致零股永远卖不出去。我处理零股的方法是检查持仓里是否有零股如果有卖出的时候把零股一起挂出去。买入则严格取整到100股。def format_buy_volume(self, volume): return (int(volume) // 100) * 100 def format_sell_volume(self, volume, available_volume): # 如果可用持仓包含零股必须全部卖出 if available_volume % 100 ! 0: return available_volume return (int(volume) // 100) * 1005.4 异常熔断机制再稳的系统也会遇到意外情况。我在交易系统里做了一个熔断开关如果连续出现多次下单失败或者单笔亏损达到设定阈值系统自动停止所有新的下单动作只保留撤单和卖出的权限然后通过告警通知你人工介入。class RiskBreaker: def __init__(self, max_fail_count3): self.fail_count 0 self.max_fail_count max_fail_count self.breaked False def on_order_error(self): self.fail_count 1 if self.fail_count self.max_fail_count: self.breaked True self._notify(连续下单失败系统已熔断)这个熔断看起来很简单但关键时刻真的能救命。我记得有一次接口参数被我改错了导致连续五六笔单子全部废单如果没有熔断机制系统会一直盲目重试交易日志里全是错误记录你根本分不清到底是策略问题还是接口问题。有了熔断问题一出现就停止能让你从慌乱中抽身出来冷静排查。6. 完整代码骨架一个可落地的交易系统6.1 整体代码结构前面讲的都是一个个模块现在把它们串成一个完整的、可直接运行的交易系统骨架。这个骨架参考了我自己实盘项目的结构简化掉了一些业务逻辑但核心脉络是完整保留的。文件组织方式trade_system/ ├── main.py # 主程序入口策略信号驱动 ├── engine.py # 交易引擎封装XtQuantTrader ├── callback.py # 回调处理类 ├── risk.py # 风控检查模块 ├── models.py # 数据模型任务表、订单表 └── config.py # 配置文件账号、策略参数这样做的好处是模块之间单向依赖main依赖engine和riskengine依赖callback和modelsrisk不依赖任何模块。改风控规则不会影响交易通道改交易通道不会影响策略层。6.2 关键类与方法的完整实现我直接放出engine.py的核心代码这是交易系统的心脏import time from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback from xtquant.xttype import StockAccount class TradeEngine: def __init__(self, path, session_id, account_id): self.path path self.session_id session_id self.account_id account_id self.account StockAccount(account_id) self.trader None self.task_map {} self._connected False self._order_seq 0 def start(self): self.trader XtQuantTrader(self.path, self.session_id) self.trader.register_callback(EngineCallback(self)) self.trader.start() result self.trader.connect() if result 0: self.trader.subscribe(self.account) self._connected True return self._connected def place_order(self, stock_code, direction, volume, price_type, price, strategy_namemain): if volume 0: return None # 生成任务ID记录策略意图 self._order_seq 1 task_id fT{int(time.time())}{self._order_seq:04d} order_id self.trader.order_stock( self.account, stock_code, direction, volume, price_type, price, strategy_name, task_id ) if order_id: self.task_map[order_id] { task_id: task_id, stock_code: stock_code, direction: direction, volume: volume, traded_volume: 0, status: submitted, create_time: time.time() } return order_id def cancel_order(self, order_id): return self.trader.cancel_order_stock(self.account, order_id) def query_asset(self): asset self.trader.query_stock_asset(self.account) if not asset: return None return { cash: asset.cash, frozen_cash: asset.frozen_cash, market_value: asset.market_value, total_asset: asset.total_asset } def query_position(self, stock_codeNone): positions self.trader.query_stock_positions(self.account) if not positions: return [] result [] for pos in positions: if pos.volume 0: continue if stock_code and pos.stock_code ! stock_code: continue result.append({ stock_code: pos.stock_code, volume: pos.volume, available_volume: pos.can_use_volume, open_price: pos.open_price, market_value: pos.market_value }) return result def query_order(self, order_id): return self.trader.query_stock_order(self.account, order_id) def reconnect(self): if self._connected: return print([TradeEngine] 尝试重新连接...) result self.trader.connect() if result 0: self.trader.subscribe(self.account) self._connected True print([TradeEngine] 重连成功) return self._connected这套代码的逻辑很直白place_order是策略层唯一调用的接口下单成功后把订单信息和策略意图绑定到task_mapquery_asset和query_position为风控和仓位计算提供数据reconnect在断线时调用。使用的时候主程序里这样组织engine TradeEngine(userdata_path, session_id, account_id) engine.start() # 假设策略信号来了 order_id engine.place_order(600000.SH, xtconstant.STOCK_BUY, 1000, xtconstant.FIX_PRICE, 10.5) # 查询任务状态 task engine.task_map.get(order_id) if task and task[traded_volume] task[volume]: print(订单已全部成交)我把这个骨架放在这个系列的每一篇里都尽量保持接口一致这样你从第一篇读到第三篇代码是可以直接拼接起来跑的。实际项目里策略层只需要关心place_order返回的order_id和task_map里的状态交易通道的细节完全被隔离在TradeEngine内部这样策略代码会干净很多。7. 实盘运行常见问题与排查速查表7.1 高频踩坑场景实盘跑了小半年我把自己踩过和身边朋友踩过的坑整理成了一张速查表写代码的时候对照着检查能省掉大量排查时间。现象可能原因排查方法连接一直失败QMT客户端没登录、userdata路径不对、session_id冲突先确认QMT客户端已登录且“交易登录”正常再检查路径和session_id回调完全不触发忘了subscribe、账户类型配置错误连接成功后务必调用subscribe检查StockAccount第二个参数是STOCK还是CREDIT下单返回空或报错账户资金不足、参数非法、股票停牌查看on_order_error回调里的error_code和error_msg订单状态一直停在“已报”行情波动大、对手盘不足按需考虑撤单重挂不要干等账户里有可用资金但下单说资金不足资金被冻结、可用资金计算口径不对查询asset.frozen_cash把冻结金额纳入计算卖出时数量不对持仓有零股、当日买入不可卖卖出可用数量用can_use_volume不要用volume撤单后重挂变成废单重挂价格越界、数量超过剩余未成交量重挂前重新做风控检查价格、数量都要过一遍程序重启后查不到前一天的订单本地任务表没有持久化把订单任务同步到数据库或文件重启时恢复状态7.2 排查方法QMT交易这块的报错信息很多场景下并不是很友好错误码的含义也比较隐晦。我的经验是遇到问题先从三个方向入手。第一个方向确认客户端侧状态。QMT客户端必须处于登录状态交易通道才能正常工作而且client要一直开着不能最小化挂后台太久。miniQMT有几个版本在客户端长时间最小化后连接会自动断掉回调也收不到这是环境问题不是代码问题。第二个方向看自己程序的日志。刚开始写交易系统的时候一定要养成打日志的习惯每次下单、收到委托回报、收到成交回报、触发风控都记录下来。实盘出问题的时候有日志和没日志的排查效率完全是两个级别。我用的格式大概是这样log_msg f[{time.strftime(%Y-%m-%d %H:%M:%S)}] order_id{order_id} stock{stock_code} volume{volume} price{price} status{status}日志里带上时间戳和订单号后面排查时会轻松特别多。第三个方向写一个简单的自检脚本。交易系统上线前我建议你先写一个脚本连接QMT、查询账户、下一笔1股的废单价格设置成0触发一个错误、撤一笔不存在的单触发错误、查询持仓。这一套流程跑下来基本上能验证出交易通道80%的功能是不是正常的。我第一次上实盘前就是这么干的当时还真发现了一个撤单接口参数顺序传反的问题如果没有提前自检实盘第一笔单子就可能出乱子。8. 最后再分享一点实盘心得我在跑实盘的初期有段时间交易系统一直很稳定我就开始放松警惕了觉得“代码没bug了”。后来一次极端情况下QMT客户端因为网络波动断线了我的程序虽然检测到了断线但重连逻辑没有加退避机制导致短时间内疯狂重连把QMT客户端直接弄到卡死。那次之后我才意识到交易系统的稳定性不是靠一次开发完成的而是靠一次次故障复盘迭代出来的。现在我的实盘程序里固定加了几条规则重连必须带指数退避第一次等3秒第二次等6秒第三次等12秒避免疯狂重试每天开盘前自动做一次通道自检和持仓对账每次下单后30秒内没有收到任何回报主动查一次订单状态并告警。这些规则看起来不起眼但实盘过程中的故障绝大多数都靠它们兜住了。miniQMT的交易系统做到这个程度已经可以满足大多数个人和中小团队的实盘需求了。下一步如果你想让系统更完善可以考虑接入数据库做订单流水持久化、加一个Web界面实时查看持仓和委托、把风控规则做成可配置的策略文件。我在后面几篇系列文章里也会陆续把这些方向展开讲尤其是“订单流水持久化”这一块实盘时间长了之后没有这个你根本没办法复盘和优化策略。