大 QMT 运行环境里的 RPC 桥接包:把大 QMT 内置 Python(行情查询、交易、持仓)封装成可远程调用的服务,并兼容一组 MiniQMT 方法名,让外部程序无需 XtQuantServer 权限就能驱动大 QMT。
支持 Redis / ZMQ / MySQL / 共享内存 四种可插拔传输,切换只需改一个配置字段。
已发布 PyPI,客户端一行安装:pip install xtquant-big-convert(详见下文「环境要求与依赖安装」)。
另附 qmt-trader skill:让 Claude Code / ZCode / Cursor 等 AI 助手通过统一 CLI(47 个子命令)直接查行情、算期权 Greeks、查持仓、下单撤单,详见下文「AI 助手 Skill:qmt-trader」。
想看跑在这座桥上的完整应用长什么样,见 bigqmt-dashboard——一个多账号持仓监控与下单面板,详见下文「基于本项目的应用」。
用微信扫码进群:
二维码会过期。 这张是 2026-09-04 生成的,微信群邀请码 7 天有效(本张到 2026-09-11)。 过期后扫码会提示无效 —— 这不是项目的问题,开个 issue 说一声,会换新的。
提 bug 和功能请求请走 issue:群里的讨论不会被检索到,而 issue 会 —— 下一个遇到同样问题的人能搜到。
如果这个项目帮到了你,欢迎给开发者买杯咖啡(完全自愿,不影响任何功能):
微信扫码赞赏。
不想手动抄两份 .example.py、也不想搞清楚三十来个键里哪些真的要改,直接跑:
bigqmt-init或者从源码检出运行:
python -m bigqmt_signal_trader.init_config问几个问题——资金账号、账号类型、传输方式(redis / zmq)、地址端口、Redis 用户名密码、是否允许远程下单、部署方式——然后把配置写出来:
| 文件 | 位置 | 作用 |
|---|---|---|
bigqmt_signal_trader_local_config.py |
QMT 的 python 目录 | 服务端(QMT 内) |
bigqmt_signal_trader_client_config.py |
你指定的目录 | 客户端(外部程序) |
BIGQMT_*_ALL_IN_ONE.py |
QMT 的 python 目录 | 选了单文件部署时,配置已烘焙进去 |
服务端和客户端两份配置由同一组答案生成,连接参数不会对不上。
几个不问、直接定死的选项:
rpc_background_threads恒为False——get_trade_detail_data离开主策略线程返回空,这不是可选项rpc_allow_order_methods默认False—— 打开前会明确提示:任何能连上这条通道的程序都可以下单- 选了无 redis 单文件会自动把传输改成 zmq,不会留下一份声称用 redis 的配置
已存在的文件会先问再覆盖(--force 跳过询问)。
密码分两类。 Redis 密码是服务凭据,写进配置文件(
.example.py本来就是这么记的),输入时不回显。QMT 登录密码不落盘——qmt_launcher从环境变量BIGQMT_LOGIN_PASSWORD读,这样它不会出现在argv或磁盘文件里,bigqmt-init沿用这个约定。生成的文件带账号和凭据,不要提交到版本库。
通过 RPC 可调用的大 QMT 能力(白名单 137 个只读方法 + 3 个下单方法(submit_order / submit_orders_batch / cancel_order)+ MiniQMT 风格别名,覆盖官方文档全部交易/查询函数):
| 类别 | 方法 |
|---|---|
| 系统 | ping |
| 行情快照 | get_ticks / get_full_tick(五档盘口) |
| 合约/品种 | get_instrument / get_instrument_type / get_stock_name / get_stock_type / get_last_close / get_last_volume / get_open_date / get_contract_expire_date / get_contract_multiplier / get_float_caps / get_total_share / get_turn_over_rate / get_weight_in_index / get_svol / get_bvol / get_risk_free_rate / is_stock_type / get_cb_info |
| K线/历史 | get_market_data / get_market_data_ex / get_local_data / get_close_price / get_index_weight |
| L2 行情 | get_l2_quote / get_l2_order / get_l2_transaction / subscribe_l2thousand(需 L2 权限) |
| 板块 | get_stock_list_in_sector / get_sector_list* / get_sector_info / create_sector / add_sector / remove_sector |
| 交易日历/时段 | get_trading_dates / get_holidays* / get_markets* / get_market_last_trade_date* / get_date_location / get_trading_calendar / get_trade_times |
| 数据下载 | download_history_data / download_history_data2 / download_holiday_data / download_etf_info / download_cb_data / download_history_contracts / download_index_weight / download_sector_data |
| 财务/因子 | get_financial_data / download_financial_data / download_financial_data2 / get_raw_financial_data / get_factor_data |
| ETF/期权/期货 | get_etf_info / get_ipo_info / get_option_list / get_his_option_list / get_his_option_list_batch / get_option_detail_data / get_option_undl_data / get_option_undl / get_ETF_list / get_main_contract / get_his_contract_list |
| 期权定价 | bsm_price / bsm_iv / get_option_iv |
| 龙虎榜/股东 | get_longhubang / get_top10_share_holder / get_holder_num / get_turnover_rate(区间换手率)/ get_industry / get_his_st_data / get_his_index_data |
| 资金流 | get_north_finance_change(北向)/ get_hkt_statistics(港股通)/ get_hkt_details / get_hkt_exchange_rate |
| 因子/模型 | call_formula / subscribe_formula / unsubscribe_formula / get_formula_result / gen_factor_index |
| 时间转换 | datetime_to_timetag / timetag_to_datetime / timetagToDateTime(纯本地计算) |
| 账户查询 | get_asset(资金)/ get_positions(持仓)/ query_stock_position(单股持仓)/ query_orders(委托)/ query_trades(成交)/ get_history_trade_detail_data(历史成交)/ get_value_by_order_id / get_last_order_id |
| 新股/打新 | get_ipo_data(返回以申购代码为键的 dict)/ get_new_purchase_limit;客户端另有 query_ipo_data / ipo_subscribe / ipo_subscribe_all,见下文 |
| 融资融券 | get_assure_contract(担保品)/ get_enable_short_contract(融券标的)/ get_unclosed_compacts(未平仓)/ get_closed_compacts(已平仓)/ get_debt_contract(负债)—— 需两融权限,普通账户降级为空 |
| 期权持仓 | get_option_subject_position(标的持仓)/ get_comb_option(组合期权) |
| 持仓同步 | sync_positions(写回 Redis 供客户端缓存) |
| 下单/撤单 | submit_order / cancel_order(默认关闭,需显式开启) |
客户端兼容层
BigQmtXtData对常用方法有显式封装(xtdata.get_longhubang(...)、xtdata.bsm_price(...)等),其余通过万能入口xtdata.call_method("get_float_caps", stockcode="000001.SZ")调用。
*标记的方法在大 QMT(完整交易端)环境下用 fallback 实现(非原生数据):get_sector_list不再静默返回兜底清单 —— 拿不到终端真实板块时直接抛错,要那 13 个常用板块名请显式传allow_fallback=True(issue #143:一份和真列表长得一模一样的假清单,调用方分辨不出来,用户自建的板块永远不出现);get_holidays从交易日历反推,get_markets返回固定市场集合,get_market_last_trade_date从日历派生。详见 docs/RPC_API_REFERENCE.md 第 8 节「大 QMT 环境的能力边界」。
-
bigqmt_signal_trader.xtquant_compat:把旧代码的xt_trader/xtdata调用转成 RPC,无需改业务代码。 -
兼容 MiniQMT 方法名:
query_stock_asset/query_stock_positions/query_stock_orders/get_full_tick/order_stock等。 -
本地 IV/Greeks fallback:
xtdata.get_option_analytics(option_code)从合约元数据和期权/标的最新 K 线 close 计算隐含波动率及 Delta/Gamma/Vega/Theta/Rho;xtdata.get_option_chain_analytics("510050.SH", "202609")一次价格批读计算整条到期月份。显式传option_price/underlying_price可改用盘口中间价。无套利边界不成立的陈旧价格按合约返回analytics_error,不会用一个伪 IV 污染整条链。原生get_option_iv保持不变,可用include_native_iv=True对照。 -
委托/成交对象补齐 MiniQMT 契约(0.3.8 起,issue #133):
query_stock_orders/query_stock_trades返回的对象新增account_type(xtconstant 数字码,取部署实际配置的类型而非硬编码 2)、instrument_name、secu_account、offset_flag、direction,成交多一个commission。strategy_name只对经本桥下的委托有效:实测 QMT 的 ORDER(120 个属性)和 DEAL(47 个属性)行上**都没有m_strStrategyName——get_trade_detail_data按策略过滤却从不回报。本桥下的单从自己的委托身份库回填(下单时按作为备注发出的user_order_id记录);手工在终端下的单没有备注,保持为空。secu_account同理恒为空:两种行都不带股东代码。字段保留是为了读到""而不是AttributeError。 -
describe_trade_detail_fields(account)诊断 RPC(0.3.8 起):返回 QMT 自己的 ORDER / DEAL 行上有哪些属性名(只返回名字不返回值)。遇到"某字段为空"时,它回答的是"是终端没给,还是桥没转发"——这两种从客户端看完全一样。 -
get_stock_type在大 QMT 上不可用,会显式抛错(0.3.8 起):服务端走ContextInfo.get_stock_type,而这个 stub 对任何代码都返回0—— 实测股票 / ETF / 债券 / 期权、以及各种代码格式全是 0。恒为 0 的"类型"比报错更糟(报错看得见,错的分类看不见),所以改成抛NotImplementedError并指向真正能用的get_instrument_type(code),后者实测能区分stock/fund/etf/bond/index。完整 QMT 的交易日 ContextInfo fallback 会把SH/SZ转成代表指数代码。委托快照增量暴露price_type/traded_price,旧 QMT 不提供时分别保持None/0.0。 -
完整 xtconstant 枚举(539 个常量,涵盖原生 MiniQMT 全部 90 个,值逐一比对无改动):账号类型、委托类型(股票/期货/信用/期权)、报价类型、委托状态、账号状态、
ORDER_TYPE_SET。
# 旧代码零改动(自动命中 shim)
from xtquant.xtconstant import STOCK_BUY, FIX_PRICE, ORDER_SUCCEEDED
# 或直接从 compat 导入
from bigqmt_signal_trader.xtquant_compat import (
SECURITY_ACCOUNT, STOCK_BUY, FIX_PRICE, CREDIT_FIN_BUY,
FUTURE_OPEN, ACCOUNT_STATUS_OK, ORDER_SUCCEEDED,
)本来就支持 —— src/xtquant/ 是一个 shim 包,提供 xtdata / xttrader / xtconstant / xttype 四个子模块。
但它和真的 xtquant 同名,所以先看清一件事:
真的 xtquant .../site-packages/xtquant (官方包)
本项目的 shim .../xtquant_big_convert/src/xtquant
两者靠 sys.path 顺序决胜,排在前面的赢。 shim 自己的文档就写着:
Put this package before the real xtquant package on PYTHONPATH only when the caller intentionally wants Big QMT RPC compatibility.
适合整套切到桥上。业务代码一个字不改:
from xtquant import xtdata
from xtquant.xtconstant import STOCK_BUY, FIX_PRICE, ORDER_SUCCEEDED
xtdata.get_full_tick(["000001.SZ"]) # 走 RPC 到大 QMT代价:整个进程里再也拿不到真的 xtquant。shim 只实现桥支持的方法,真包里的其他东西就没了。
适合渐进迁移。不让 shim 进 sys.path,改成显式:
from bigqmt_signal_trader.xtquant_compat import xtdata as bq_xtdata
from bigqmt_signal_trader.xtquant_compat import (
XtQuantTrader, StockAccount, XtQuantTraderCallback,
STOCK_BUY, FIX_PRICE, ORDER_SUCCEEDED,
)
from xtquant import xtdata # 真包,完全不受影响老代码继续用真 xtquant,新代码走桥,按模块逐步迁。
普通 pip install xtquant-big-convert 会把 shim 的 xtquant/ 装进 site-packages,和真包同名同目录。两个 pip 包争同一个路径,谁后装谁覆盖,pip uninstall 其中一个还可能把另一个的文件带走。
| 你要的 | 装法 |
|---|---|
| 方案 A(shim 顶替),且本机没有真 xtquant | 普通 pip install xtquant-big-convert |
| 方案 B(两个都留着) | editable 安装:pip install -e /path/to/xtquant_big_convert —— 它不往 site-packages 写 xtquant/,只加一条路径,随时能靠调整 sys.path 顺序切换 |
| 按环境切换(本机真包、服务器走桥) | 虚拟环境隔离 + PYTHONPATH 控制顺序 |
确认当前谁生效:
import importlib.util
print(importlib.util.find_spec("xtquant").origin)
# ...site-packages/xtquant/__init__.py -> 真包
# ...xtquant_big_convert/src/xtquant/... -> shim客户端注册 XtQuantTraderCallback 子类,connect()/subscribe() 后实时接收委托/成交/错误回报(通过 Redis pubsub 推送):
from bigqmt_signal_trader.xtquant_compat import (
StockAccount, XtQuantTraderCallback, configure, xt_trader,
)
class MyCallback(XtQuantTraderCallback):
def on_stock_order(self, order):
print("委托回报:", order.stock_code, order.order_status, order.order_sysid)
def on_stock_trade(self, trade):
print("成交回报:", trade.stock_code, trade.order_id, trade.traded_volume, trade.traded_price)
def on_order_error(self, order_error):
print("委托失败:", order_error.order_id, order_error.error_id, order_error.error_msg)
def on_cancel_error(self, cancel_error):
print("撤单失败:", cancel_error.order_id, cancel_error.error_id, cancel_error.error_msg)
def on_order_stock_async_response(self, response):
print("异步下单回报:", response.account_id, response.order_id, response.seq)
def on_account_status(self, status):
print("账户状态:", status.account_id, status.account_type, status.status)
configure()
xt_trader.register_callback(MyCallback())
acc = StockAccount(xt_trader.client.account_id, "STOCK")
xt_trader.connect()
xt_trader.subscribe(acc)
# 异步下单(返回 seq,回报走回调)
seq = xt_trader.order_stock_async(acc, "600654.SH", 23, 100, 11, 2.95, "rpc_test", "备注")完整的回调链(对齐 MiniQMT 原生语义,实盘验证):
| 回调 | 触发时机 | 已验证 |
|---|---|---|
on_account_status |
connect()/subscribe() 后 |
✅ |
on_order_stock_async_response(seq, resp) |
异步下单提交成功 | ✅(实盘) |
on_stock_order(order) |
委托状态变化(已报 50 / 已成 56 / 废单 57) | ✅(实盘) |
on_stock_trade(trade) |
成交回报 | ✅ |
on_order_error(err) |
废单/拒单(服务端检测 status=57 推送) | ✅(实盘) |
on_cancel_error(err) |
撤单失败 | ✅ |
on_cancel_order_stock_async_response |
异步撤单回报 | ✅ |
异步下单的事件顺序(Issue #51):on_order_stock_async_response(异步下单工作线程)与 on_stock_order / on_stock_trade(Redis pub/sub 监听线程)走不同通道,服务端在 order_callback 里先推事件、后回 RPC,事件先于响应是常态而非偶发竞态。客户端按 order_remark 设屏障:命中待响应委托的事件先暂存,response(或 error)触发后按到达顺序放行;屏障 10 秒超时兜底——丢事件比顺序错乱更糟。延迟只加在 order_stock_async 路径上:手工下单、同步下单、无 remark 的委托一律直通。成交事件可能没有 remark,此时按委托事件学到的 order_sys_id 关联。order_remark 不强制唯一(网格类策略常复用):同 remark 的后一笔下单会接管前一笔的屏障并先放行其暂存事件,response 按 seq 精确匹配,前一笔的 response 不会误放后一笔的屏障。已验证:单测(含反向验证)+ 盘后真实 Redis 注入实测(部署环境保序成立)。
*_async 查询方法(对齐 MiniQMT 签名,callback 可选):
# 方式 1:callback 接收结果(MiniQMT 原生语义,返回 None)
xt_trader.query_stock_asset_async(acc, lambda asset: print(asset.cash, asset.total_asset))
xt_trader.query_stock_positions_async(acc, lambda positions: print(len(positions)))
# 方式 2:不传 callback,返回 seq(我们的扩展)
seq = xt_trader.query_stock_orders_async(acc)注意:QMT 必须运行在实盘模式(非模拟/模型交易)才能收到完整回报。模拟模式下委托进 QMT 界面但不在真实委托队列,query_orders 查不到、order_stock 返回 -1(触发 on_order_error)。
from bigqmt_signal_trader.xtquant_compat import xt_trader, StockAccount
acc = StockAccount("你的账号")
# 1) 看今天有什么可申购(只读)
for code, info in xt_trader.query_ipo_data(acc, stock_type="STOCK").items():
print(code, info["name"], info["issuePrice"], info["maxPurchaseNum"])
# 301689.SZ 某某科技 16.0 12000
# 2) 先看计划,不下单
for row in xt_trader.ipo_subscribe_all(acc, dry_run=True):
print(row)
# {'stock_code': '301689.SZ', 'action': 'planned', 'volume': 12000, 'price': 16.0, ...}
# 3) 真申购(沪深;北交所默认排除)
results = xt_trader.ipo_subscribe_all(acc)每只返回 action(subscribed / planned / skipped / failed)与 reason,一只失败不影响其余。
这是一个你主动调用的方法,不是桥自己会做的事。 它会下真实委托,所以必须是当天有人明确要求,而不是升级后自动发生。要每天定时打新,请在你自己的程序里调度它。
因为走的是既有的 order_stock 通道,它自动获得:
rpc_allow_order_methods |
和其他委托一样保持 opt-in,默认关 |
orderType 1101 / prType 11 / quickTrade 2 |
网关默认值 —— quickTrade 必须是 2,见 API 参考 1.4:定时器/回调中下单传 1 可能静默不发出 |
| 主线程执行、委托记账、exec 事件 | 与普通下单完全一致 |
默认只打沪深。 沪深打新是市值申购、不冻结资金;北交所需要冻结资金,因此默认排除。需要时显式打开:
xt_trader.ipo_subscribe_all(acc, markets=("SH", "SZ", "BJ"))申购代码无法识别时会跳过,不会猜。 申购代码有自己的编号(沪 730/732/780/787/789,深 00/30,北 920/889/8/4),认不出来的代码一律跳过——在一个会下单的路径上,猜错的代价不对称。
query_new_purchase_limit(acc) 返回各板块申购额度(dict)。
实盘验证到
dry_run为止:query_ipo_data与申购计划均已在大 QMT 上验证正确(2026-08-28,301689.SZ @ 16.0 × 12000)。真实申购会下真实委托,未在本仓库验证过。
subscribe_whole_quote 是服务端真推送——对齐 MiniQMT 全推行情订阅。服务端引用计数管理大 QMT 行情回调,通过独立 PUB/SUB 通道向客户端增量推送行情(不是一次性快照):
架构(三通道):
- 控制面 RPC——
subscribe_whole_quote/unsubscribe_whole_quote/quote_keepalive方法(复用现有 transport) - 数据面推送——
QuotePushChannel单向 PUB/SUB(redis pub/sub 或 zmq PUB/SUB,按部署 transport 选择;msgpack 编码 + json 兜底) - Big-QMT 行情源——
QuoteSubscriptionManager按组合键归一化共享(大写/去空格/排序),多客户端共享一个底层订阅
关键设计:
- 组合键去重:不同客户端订阅相同标的组合,只占一个 big-QMT 订阅
- 引用计数:按
(client_id, sub_id)计数,全部退订或 30s keepalive 超时才销毁 - 客户端心跳:周期
quote_keepalive;检测推送静默(默认 10 轮心跳)自动重放订阅,服务端重启后自动恢复 - 初始快照:客户端用
get_full_tick预拉快照(big-QMT 回调是增量的) - 期权兼容:显式
.SHO/.SZO合约在部分完整大 QMT 版本中不会从subscribe_whole_quote推送,因此服务端对这些代码逐合约使用ContextInfo.subscribe_quote(..., result_type="list");股票、ETF 和市场代码仍走原有全推路径。混合组合对客户端保持一个订阅号。
用法:
from bigqmt_signal_trader.xtquant_compat import configure, xtdata
configure()
# 订阅全推行情(callback 收到增量推送)
def on_quote(data):
for code, tick in data.items():
print(code, tick.get("lastPrice"))
seq = xtdata.subscribe_whole_quote(["600000.SH", "000001.SZ"], callback=on_quote)
# 退订
xtdata.unsubscribe_quote(seq)验证:实盘交易日验证 1/20/50/100 只标的,3s 推送节奏稳定,零丢失零乱序;多客户端共享/退订隔离/同客户端多 sub_id 全过;服务端重启恢复(42s 中断后验证两次)。另在完整大 QMT 2.1.19.0 盘中验证显式 .SHO 快照、500ms 实时推送及 ETF+期权混合组合。详见 docs/SUBSCRIBE_WHOLE_QUOTE_PUSH.md 和 docs/SUBSCRIBE_WHOLE_QUOTE_LIVE_VERIFICATION.md。
市场令牌返回的是交易所挂牌的全部标的,股票只占一小部分。 实测上交所 "SH" 共 26744 个标的,其中股票 2315 只(8.7%),其余是债券(36%)、回购等。QMT 的耗时严格线性、约 0.29ms/只,所以全量要 7.4s,只取股票 0.9s。
从 0.2.15 起 默认只取股票:
xtdata.get_full_tick(["SH"]) # 2315 只 1.08s ← 新默认
xtdata.get_full_tick(["SH"], types=["all"]) # 26744 只 7.39s ← 旧行为
xtdata.get_full_tick(["SH", "SZ"]) # 5216 只 1.66s
xtdata.get_full_tick(["SH"], types=["stock","etf"])
xtdata.get_full_tick(["600000.SH"]) # 显式代码不受影响这是破坏性变更。 如果你的代码依赖
get_full_tick(["SH"])返回债券 / 回购 / ETF,请显式传types=["all"]。收窄发生时会打印一次提示,便于发现:[bigqmt_market] SH narrowed to 2315 stock; pass types=['all'] for every instrument the exchange lists
types |
板块 | 约数 |
|---|---|---|
stock(默认) |
上证A股 / 深证A股 / 京市A股 | 2315 / 2901 / 339 |
etf |
沪深ETF | 1696 |
fund |
沪深基金 | 2249 |
index |
沪深指数 | 609 |
convertible |
沪深转债 | 320 |
all |
不收窄,返回交易所全部标的 | 26744(SH) |
关键在于请求时就收窄,而不是拿回来再过滤——事后过滤仍要付 QMT 对每个多余标的的 0.29ms。板块清单由 FormulaServer 直连提供(实测 13ms)并按运行缓存,相对省下的时间可以忽略。
收窄失败时退回全量,不返回空:板块查不到、类型不认识、该市场没有对应板块(如 HK),都保留市场令牌照旧请求。丢行情比慢更糟。
显式传超大代码列表会超时:26744 个代码显式传入会打爆单次 RPC 超时,而市场令牌可以。要全量请用令牌 + types=["all"]。
不传 field_list 表示"要全部字段",返回 11 列,只能走 RPC:
field_list=[] 0.97s 11 列(含 preClose / suspendFlag 等)
field_list=[open,high,low,close,volume,amount]
0.03s 6 列 ← FormulaServer 直连,约 30 倍
这不是可以自动优化掉的差距。 FormulaServer 只供那 6 列,其余 4 列返回 NaN,而 RPC 有真实值(实测 preClose 9.07 / 7.82 / 11.59,直连全部为 nan)。把默认路由到直连会静默把真实价格换成 NaN,所以默认保持走 RPC。
只要 OHLCV 就显式写出来,那 30 倍就到手了。首次不传 field_list 时会在 bigqmt.log 记一条说明。
重启策略后,第一次调用 get_financial_data 可能要几分钟。 实测过一次 346 秒——当时 QMT 自身完全健康(全推行情每几秒一批、线程池正常),主策略线程也空闲(adjust 每 10 秒 100 拍,每拍 < 2ms)。当天之后的所有调用都在 1 秒内,包括从没查过的票和没查过的表,所以这是一次性代价,不是按代码的缓存未命中。
问题在于它的传染性:RPC 处理是串行的,一个调用卡住,后面排队的全部超时。客户端看到的是一片超时,和「桥死了」完全一样。
启动后会在后台线程上先跑一次这个调用,把这份等待提前付掉:
[bigqmt_warmup] get_financial_data: first call after a restart can take
minutes; running it now so a caller does not have to wait
[bigqmt_warmup] get_financial_data warm after 346.0s -- that wait is now paid
热了之后就是这样:
[bigqmt_warmup] get_financial_data warm in 0.31s
预热不会让这个代价变便宜,它只是把代价挪到一个确定的时刻、一个没人等待的线程上,并且留下一行说明——而不是让它以「第一个调用方莫名卡死」的形式出现。
为什么不放在 init 里? 启动诊断(
_diag_startup)跑在主线程的init()中。把一个可能 346 秒的调用加进去,会在 adjust 定时器都还没排上的时候冻住整个启动——比原问题更糟。所以预热走独立守护线程,init()立即返回。
关掉它(服务端 local config):
BIGQMT_REDIS_CONFIG = {
# ...
"warm_context_data": False,
}handler 还在跑的时候就会报,不用等它结束:
[bigqmt_rpc] zmq handler STILL RUNNING method=get_financial_data 40s
thread=bigqmt-zmq-rpc queued=3 -- the bridge is blocked, not dead
- 默认 20 秒触发。比实测最慢的健康调用(整市场快照 7.7s)长得多,又短于客户端 30 秒的默认超时,所以日志会在调用方放弃之前就点名
- 指数退避,一次长阻塞不会把它自己要解释的日志淹掉
- 调整或关闭(注意它在
zmq子块里,不是顶层):
BIGQMT_REDIS_CONFIG = {
# ...
"zmq": {
"stall_warn_seconds": 45, # 0 = 关闭
},
}看到成片超时时,先在服务端日志里搜 STILL RUNNING 或 slow handler。 有这两行之一,就说明桥没死,只是被一个慢调用堵住了——等它跑完,或者查那个方法。
注意
slow handler是事后打的(handler 返回才计时),STILL RUNNING才是进行中的。
部署到 QMT 是文件拷贝,而 QMT 跨策略重跑保留 sys.modules。所以「忘了拷」和「拷了但没被加载」从外部看一模一样——这是本项目最容易浪费时间的一类问题:本地修好了,实盘却像没修。
启动时会打印实际加载的版本和目录:
[bigqmt_shell] bigqmt_signal_trader 0.2.15 loaded from D:\...\python\bigqmt_signal_trader
客户端可以直接问:
xtdata.get_deployment_info()
# {'version': '0.2.15',
# 'package_dir': 'D:\...\python\bigqmt_signal_trader',
# 'qmt_python_dir': 'D:\...\python',
# 'strategy_dir': 'D:\...\python',
# 'python_version': '3.6.8'}版本不一致时,连接会告警:
[WARNING] version mismatch: this client is 0.2.15, the QMT-side bridge is 0.2.9.
A copy alone does not take effect -- QMT keeps modules across strategy re-runs,
so the strategy must be restarted too. Set BIGQMT_AUTO_SYNC=1 (or call
xt_trader.sync_deployment()) to push this client's package into the QMT python
directory.
xt_trader.sync_deployment(dry_run=True) # 先看会动哪些文件
xt_trader.sync_deployment() # 真同步目标目录来自 get_deployment_info(),不必硬编码路径。
设环境变量 BIGQMT_AUTO_SYNC=1 后,连接时检测到版本不一致会自动同步。默认关闭——往实盘终端写文件不该是"连接"的副作用,源码树里若有半成品会直接进实盘。
| 行为 | 说明 |
|---|---|
| 绝不写入配置文件 | bigqmt_signal_trader_local_config.py / bigqmt_signal_trader_client_config.py 存账号和凭据;对应的 .example.py 属文档,会更新 |
| 不新增顶层文件 | 只刷新部署里已有的模块,加上策略入口(全新部署需要它)。否则 QMT 目录会变得没人说得清 |
| 覆盖前备份 | 每个被覆盖的文件留 .bak_<时间戳> |
| 原子写入 | 先写临时文件再替换,中断不会留下半个模块 |
同步之后必须让策略重新加载。 QMT 跨重跑保留
sys.modules,拷贝本身不生效——每次同步结果都带restart_required并在日志里提示。
xt_trader.reload_deployment("why") # -> {'scheduled': True, 'version_before': '0.3.7'}
xt_trader.reload_status() # -> {'ok': True, 'modules_purged': 28,
# 'version_before': '0.3.7',
# 'version_after': '0.3.8', 'seconds': 0.79}把所有 bigqmt_signal_trader.* 从 sys.modules 清掉、重新绑定策略模块 import 时持有的引用、再跑一次 init() 重建对象图。约 0.8 秒。
只是"已排期":执行它要 reset_app(),那会停掉正在应答这个请求的 RPC 服务,所以回复必须先发出去;真正的重载在下一个 adjust tick 上做,轮询 reload_status() 看结果。期间约 1 秒的查询会超时(服务正在重建)。
| 能刷新 | bigqmt_signal_trader/ 下的一切——适配器、RPC handler、models、传输层 |
| 刷新不了 | bigqmt_signal_trader_strategy.py 和 BIGQMT_REDIS_DRYRUN.py。QMT 自己 exec 这两个文件,模块没法 reload 自己所在的模块——改这两个仍要重启策略 |
用 purge 而不是 importlib.reload:reload 必须按依赖顺序(order_bigqmt 在 import 时 from ..models import OrderSnapshot,顺序错了会静默留住旧类),purge 没有顺序问题。
同步逻辑跑在客户端,不在 QMT 里。 让交易进程盘中改写自己的代码,等于把源码树里的任何东西(包括改到一半的)直接送上实盘。
实测 p50(实盘终端,收盘后,schedule_adjust_interval: "100nMilliSecond",
每格重启策略后现测。ping 走 inline,query_stock_positions 走 deferred——
必须回主线程,是交易查询的真实代价):
| 传输 | ping p50 | 交易查询 p50 | 串行吞吐 | 跨机 | 适用场景 |
|---|---|---|---|---|---|
| redis(默认) | 10ms | 4ms | 20 / 195 次每秒 | ✅ | 生产默认,也是最快的 |
| zmq + drain | 95ms | 95ms | 10 / 10 次每秒 | ✅ | 无 redis 时的同机方案 |
| zmq + 后台线程 | 405ms | 607ms | 2.4 / 1.7 次每秒 | ✅ | 旧默认,不推荐 |
| mysql | ~105ms | — | — | ✅ | 兼容兜底 |
| shm | — | — | — | ❌ | 接口预留(未实现) |
这张表在 0.3.21 之前是反的,写着 zmq「同机低延迟 p50
0.7ms」、redis 13ms。 那个 0.7ms 是撞上 adjust 空窗的最好情况,不是 p50;redis 的 13ms 一直是准的。 实测 **redis 比 zmq 快 860 倍**,而且只有 redis 上多线程并发能提升吞吐—— zmq 客户端整个请求周期持单 socket 锁,并发拿不到任何收益(#186)。transport没有特别理由就别改。
大 QMT 的 58600 端口是 FormulaServer——QMT 内置的 C++ 行情/参考数据服务(端口取自
config/formulaserver/formulaserver.ini 的 [server_formula] address)。QMT 自带 Python
的 qmt_api 包就是它的客户端。
客户端对这些方法会绕开整条 RPC 链路(不经过 QMT 的 python 策略线程,也不抢 GIL), 实测 p50 0.07ms,穿过完整客户端栈是 0.145ms/次:
| 对比 | p50 |
|---|---|
| redis RPC | ~10ms |
| zmq RPC(drain) | ~95ms |
| FormulaServer 直连 | 0.07ms(无 GIL 竞争) |
直连覆盖 10 个方法:get_instrument / get_instrument_detail / get_instrumentdetail /
get_last_volume / get_total_share / get_contract_multiplier / get_main_contract /
get_weight_in_index / get_stock_list_in_sector / get_market_data_ex。
能力边界(重要):FormulaServer 只有行情/参考数据。所有账户、持仓、委托、成交、下单
方法一律返回 ErrorID 200005 未找到该服务,getFullTick/getQuote 也不存在。所以它是
只读快速路径,不是 RPC 桥的替代品——交易、账户查询、五档盘口仍然走 RPC。
以下方法刻意不走直连,因为参数语义与我们的调用方不一致,宁慢勿错:
get_trading_dates—— FormulaServer 要股票代码(000001.SZ),传市场代码(SH)静默返回[],而我们的调用方传的是市场。get_divid_factors/get_risk_free_rate—— 参数语义不同(区间 vs 单日、index vs timetag)。- 复权 K 线 —— 实测
dividendType传none和front返回完全相同,复权未生效。因此只有dividend_type="none"才走直连,其余回退 RPC,避免静默返回未复权价格。 (复权数据还需先在服务端下载原始数据,见下文「复权数据下载陷阱」。)
配置(客户端侧,默认就是开启,通常不用写):
BIGQMT_REDIS_CONFIG = {
"formula_server": {
"enabled": True, # 或环境变量 BIGQMT_FORMULA_ENABLED=0 关闭
# "host": "127.0.0.1", # 默认本机;FormulaServer 绑 0.0.0.0,跨机需放行防火墙
# "port": 58600, # 不写则从 qmt_root 的 ini 读,再退回 58600
# "qmt_root": r"D:\国金证券QMT交易端",
# "timeout_seconds": 3.0,
# "methods": ["get_instrument"], # 只路由白名单里的方法
# "failure_cooldown_seconds": 30.0, # 连不上后停用多久再重试
},
}失败一律自动回退 RPC:方法未映射、参数translate 不了、服务没起、连接断——都退回原路径,
所以连不上 58600 的客户端行为与改动前完全一致。BSON 编解码内置了无依赖实现(可选用
pymongo 的 bson,两者输出实测逐字节一致),客户端不需要额外装包。
大 QMT 基本每天早上要重启一次,卡点在登录框。两条路绕过它:
依赖:进程枚举优先用
psutil;Win11 起系统不再带wmic,没有 psutil 时close_qmt/status会直接报cannot enumerate processes(issue #128)。 装上即可:pip install psutil。
python -m bigqmt_signal_trader.qmt_launcher status --dir "D:\国金证券QMT交易端_lemo"
python -m bigqmt_signal_trader.qmt_launcher restart --dir "D:\国金证券QMT交易端_lemo"| mode | 做什么 | 需要登录框交互 |
|---|---|---|
linkmini |
XtMiniQmt.exe linkMini,MiniQMT 免密启动 |
否 |
bat |
跑指定批处理(如 免密登录qmt.bat) |
否 |
exe |
直接起 XtItClient.exe,靠终端自身恢复会话 |
否 |
login |
起 exe 后向登录框输入账号密码 | 是,需 pywin32 + pyautogui |
⚠️ linkmini对本项目不可用:它起的是迷你终端(MiniQMT),没有策略编辑器和 ContextInfo 运行时,桥作为大 QMT 策略跑不进去。本项目的桥必须用exe/bat/login三种模式(都起大终端)。linkmini只在你同时需要迷你终端(给外部 xtquant SDK 提供行情/交易服务)时才有意义——那是另一个进程,与桥互不影响。
login 模式需要未锁屏的交互式桌面。 它用的是 keybd_event / mouse_event
物理输入(经 ctypes),不是 SendMessage——消息式输入投不到 Qt 对话框的焦点控件上,
当别的窗口在前台时会静默失败,什么也不输入。物理输入要求对话框在最前,所以启动前会
先把它置顶并核验;锁屏或 RDP 注销的会话直接抛 QmtLauncherError 而不是打一半密码。
需要无人值守定时重启(重启的是大终端+桥策略)的话,用
bat/exe/login三种模式。bat/exe不碰登录框、锁屏也能跑,但要求终端自身能恢复会话(设了自动登录);login会替你输密码,但受锁屏限制。
密码从环境变量 BIGQMT_LOGIN_USER / BIGQMT_LOGIN_PASSWORD 读,不走命令行参数——argv
对同机任何进程可见。
除了命令行,也可以在代码/计划任务脚本里直接调函数(语义与 CLI 一致):
from bigqmt_signal_trader.qmt_launcher import (
close_qmt, open_qmt, restart_qmt,
is_qmt_running, find_qmt_processes, wait_until_ready, session_is_locked,
)
# 关:先礼貌 terminate(QMT 会冲刷本地数据),force_after_seconds 后才强杀。
# 只终结该安装目录 bin.x64 下的进程;拿不到 exe 路径的进程直接跳过而不是误杀。
close_qmt(r"D:\国金证券QMT交易端_lemo", force_after_seconds=20)
# 开:mode 见上表(exe/bat/login;linkmini 对本项目不可用)。
# login 模式自动填账号密码:Alt 解锁前台 + 置顶 + 字段级像素验证打字,
# 打完逐段验证(账号必须进账号区、密码必须进密码区),错了清空中止,不提交错表单。
open_qmt(
r"D:\国金证券QMT交易端_lemo",
mode="login",
credentials={"user": "你的账号", "password": "你的密码"},
window_title_prefix="QMT", # 登录框标题包含串(模拟端 "国金QMT交易端模拟" 也能匹配)
ready_timeout_seconds=180, # 等 FormulaServer(58600) 就绪的超时
)
# 一把重启:close_qmt → 等端口释放 → open_qmt。会话锁屏且需要 login 时直接抛错
# (而不是关掉终端却登不回去)。
restart_qmt(r"D:\国金证券QMT交易端_lemo", mode="login",
credentials={"user": "...", "password": "..."})
# 状态查询
is_qmt_running(r"D:\国金证券QMT交易端_lemo") # 进程在不在
find_qmt_processes(r"D:\国金证券QMT交易端_lemo") # [(pid, 进程名, exe 路径)]
wait_until_ready(port=58600) # 阻塞到 FormulaServer 可连接
session_is_locked() # 交互式会话是否锁屏两个设计要点:
- 按安装目录隔离。同机常并行跑多个 QMT,
taskkill /im XtItClient.exe会误杀别人的 实盘。这里只终结--dir对应bin.x64下的进程;拿不到 exe 路径的进程直接跳过而不是 猜。 - 等就绪而不是 sleep 固定秒数。启动完成的判据是 FormulaServer 端口(58600)能接受连接,
超时抛
QmtLauncherError而不是静默返回,避免定时任务在没起来的终端上继续跑。
restart 默认在关闭后等 5 秒再启动:ZMQ 传输是精确绑定配置端口(不扫描),socket 没
完全释放就重启会绑定失败。
bigqmt_backtest 与实盘 RPC 桥接完全分离,提供两个明确隔离的后端:
QMT_NATIVE:BIGQMT_ZMQ_BACKTEST.py运行在 QMT 回测进程内。QMT 负责历史 行情推进、资金持仓、passorder/cancel和原生撮合;ZMQ 只桥接 Bar、订单意图及 QMT 委托/成交结果。LOCAL_SIM:端口16661的独立 CSV 工具,仅用于脱离 QMT 验证协议和策略逻辑, 使用本地撮合并输出本地结果文件。
QMT 原生入口使用独立端口 16662、独立 run_id/client_id,强制验证
ContextInfo.do_back_test=true,固定 live_ready=false,不会导入或修改
bigqmt_signal_trader。
启动 CSV 独立测试服务:
python -m pip install -e .
python -m bigqmt_backtest.server `
--data examples/backtest_bars.example.csv `
--config examples/backtest_config.example.json `
--run-id demo-001 `
--bind tcp://127.0.0.1:16661另开一个终端运行外部策略:
python examples/zmq_backtest_strategy.py `
--endpoint tcp://127.0.0.1:16661 `
--run-id demo-001 `
--symbol 600000.SH `
--fast 2 `
--slow 3QMT 原生安装、逐 Bar 同步协议、CSV 备用模式和安全边界见 docs/ZMQ_BACKTEST_BRIDGE.md。
如果你的 QMT 环境拒绝 import redis(券商白名单拦截),用 bigqmt_no_redis/ 目录下的无 redis 版本:
bigqmt_no_redis/zmq_transport.py— 自包含的 ZMQ transport,内联所有编码函数,完全不 import redis_common/redis_rpc,去掉 redis 服务发现(用静态派生端口)bigqmt_no_redis/DRYRUN_no_redis.py— 无 redis 的 DRYRUN 入口,强制transport=zmq+background_threads=True,只加载 zmq transport
用法:QMT 策略编辑器加载 BIGQMT_DRYRUN_NO_REDIS.py(同步到 QMT 目录时用这个文件名),RPC 走纯 ZMQ,零 redis 依赖。其余功能(行情/交易/持仓查询)与标准版一致。
部分券商的 QMT 更严:白名单 + 不能加载文件、不能 import 外部模块,只有把所有代码放进一个策略文件才能跑(Issue #56)。tools/ 下两个生成器负责把整个包打成一个自包含文件:
python tools/build_single_file.py
python tools/build_no_redis_single_file_flat.py| 生成器 | 产物 | 内嵌方式 | 用于 |
|---|---|---|---|
build_single_file.py |
src/BIGQMT_REDIS_DRYRUN_ALL_IN_ONE.py |
base64 | redis / zmq 均可 |
build_no_redis_single_file_flat.py |
src/BIGQMT_DRYRUN_NO_REDIS_FLAT_ALL_IN_ONE.py |
明文真实代码 | 沙箱拒绝 import redis 时,强制 ZMQ |
两者都内嵌 bigqmt_signal_trader 全部子模块 + bigqmt_signal_trader_strategy + bigqmt_signal_trader_redis_rpc_runtime,运行时用自定义 import 钩子从内存解析,不从磁盘 import 任何自定义模块;只依赖标准库和第三方库(redis / zmq / pandas)。
flat 版把每个模块缩进进 def _mod_N(): 函数体、再用其 __code__ 在独立模块命名空间里 exec,所以内嵌源码在生成文件里可搜索、可阅读、可直接改,IDE 也能高亮跳转。它处理了两个坑:用 tokenize 保护多行字符串内部不被缩进改动;用 AST 收集模块级绑定名并在函数体开头注入 global,否则被嵌套函数闭包引用的模块级名字会变成 cell 变量,与 global 更新的模块 dict 失去同步。
函数体 exec 也正是
from X import *变成SyntaxError: import * only allowed at module level的原因(Issue #76)。整个包因此不允许出现星号导入,tests/test_single_file_build.py会守住这条。
用法:编辑生成文件顶部的 config block(BIGQMT_ACCOUNT_ID / BIGQMT_ACCOUNT_TYPE / BIGQMT_REDIS_CONFIG),把这一个文件拷进 QMT 的 python 目录当策略加载即可,不需要一并拷贝整个包。默认值与 src/bigqmt_signal_trader_local_config.example.py 保持一致——rpc_allow_order_methods 默认为 False,需要远程下单/撤单时才显式打开。
产物约 900KB / 700KB,已加入 .gitignore——用时重新生成,不要提交。改动包内代码后需重新运行生成器。
感谢 @heimo88 提供这两个脚本并在其券商环境实测。
get_trade_detail_data 按 strategy_name 过滤委托/成交——下单时用的 strategy_name 必须和查询时一致,否则查不到。
- 下单时传
strategy_name="rpc_test"→ 委托记在rpc_test下 - 查询时传
strategy_name="bigqmt_signal_trader"→ 返回空(不匹配)
修复:query_orders / query_trades 默认传空字符串 "",返回该账户的全部委托/成交(不按 strategy_name 过滤)。如需过滤,显式传 strategy_name。
实测验证(get_trade_detail_data 探测):
st=""→ ORDER=9, DEAL=9(全部)st="rpc_test"→ ORDER=3, DEAL=1(只有 rpc_test 的)st="bigqmt_signal_trader"→ ORDER=0, DEAL=0(空)
前/后复权 K 线必须先在服务端下载原始数据,否则返回全 0。
Big QMT 的复权(dividend_type='front'/'back')是服务端现场计算的——需要原始 K 线 + 除权因子已经在服务端存在。直接请求 front 而服务端没下载过原始数据时,返回的 close 全是 0.0(只有最后一根有价)。
实测复现(600654.SH / 600227.SH):
- 直接
get_market_data_ex(dividend_type='front')→ 634 行全 0 - 先
download_history_data后再请求 → 真实复权价(front ≠ none,复权生效)
已修复:xtdata.download_history_data2(codes, period, dividend_type='front') 现在会自动先触发服务端原始数据下载(拉原始 K 线 + 除权因子),再拉复权数据到本地缓存。用法不变:
# 前复权下载(自动先服务端下载原始数据 + 除权因子)
xtdata.download_history_data2(["600654.SH"], period="1d",
start_time="20240101", dividend_type="front")
# 之后本地读取(零 RPC)
xtdata.get_local_data(["close"], ["600654.SH"], period="1d",
start_time="20240101", dividend_type="front")读取类 API 也自愈:get_market_data_ex / get_market_data 带复权参数时,若检测到返回全 0(服务端缺原始数据),会自动触发服务端下载、等待落盘、重试一次,拿到真实复权价。get_local_data 的 fallback 拉取同样受益。无需手动等待。
注意:QMT 服务端下载是异步落盘的,自愈路径内置了等待 + 一次重试;极端大区间若一次重试仍全 0,可稍后重读或先显式 download_history_data2。
实盘发现:QMT 回调里 m_nDirection 恒为 48(即使是卖出),导致卖出被误判为买入。
修复(exec_events._extract_direction)改为仲裁链:
m_nOffsetFlag(最可靠,匹配query_orders)m_nDirection(传统 EEntrustBS,但实盘可能恒为 48)- 当 direction≠offset(期货:卖+开仓=49+48),用
m_nOpType(23=买/24=卖)仲裁 m_nOpType/order_type(兜底)
对股票现货,direction=offset(48=买/49=卖);对期货,direction≠offset,仲裁保正确。
当前架构是单账号单实例——一个 QMT 策略进程绑定一个账号,RPC channel 按 account_id 隔离(bigqmt:rpc:req:{account_id})。多账号场景(如股票+期货、普通+信用账户同时交易)的推荐方案是在 QMT 里跑多个策略实例,每个实例绑一个账号。
服务端(QMT 内):为每个账号创建一个独立的配置文件和 DRYRUN 入口。
# bigqmt_signal_trader_local_config_stock.py — 股票账号
BIGQMT_ACCOUNT_ID = "你的股票账号"
BIGQMT_REDIS_CONFIG = {
"host": "...", "port": 6379, "db": 5, "password": "...",
"transport": "redis", # 或 "zmq"
"account_type": "STOCK", # 股票
# ...
}
# bigqmt_signal_trader_local_config_credit.py — 信用账号
BIGQMT_ACCOUNT_ID = "你的信用账号"
BIGQMT_REDIS_CONFIG = {
"host": "...", "port": 6379, "db": 5, "password": "...",
"transport": "redis",
"account_type": "CREDIT", # 信用(两融)
# ...
}然后在 QMT 策略编辑器里加载两个 DRYRUN 文件(每个指向不同的配置),分别运行。两个实例的 RPC channel 自动隔离(按 account_id)。
zmq 模式注意:每个实例的 zmq 端口从 account_id 派生(
15560 + account_id mod 100),不同账号自动不冲突。
客户端(外部程序):为每个账号创建独立的 client/trader 对象。
from bigqmt_signal_trader.xtquant_compat import BigQmtRpcClient, BigQmtXtTrader, StockAccount
# 股票账号
stock_client = BigQmtRpcClient(account_id="股票账号", redis_config={...})
stock_trader = BigQmtXtTrader(account_id="股票账号", redis_client=stock_client.redis_client)
stock_acc = StockAccount("股票账号", "STOCK")
# 信用账号
credit_client = BigQmtRpcClient(account_id="信用账号", redis_config={...})
credit_trader = BigQmtXtTrader(account_id="信用账号", redis_client=credit_client.redis_client)
credit_acc = StockAccount("信用账号", "CREDIT")
# 分别查询/下单
stock_asset = stock_trader.query_stock_asset(stock_acc)
credit_positions = credit_trader.query_stock_positions(credit_acc)跨账号隔离:每个账号的 RPC channel、持仓查询、委托回报完全隔离(按
account_id路由),互不影响。
本项目的目标是让照着 MiniQMT (xtquant) 写的代码不改就能跑。下表列出返回值契约——类型不对不会报错,只会让判断悄悄反过来,所以单独列出来。
| 接口 | 返回 | 说明 |
|---|---|---|
order_stock() |
int |
成功为正数,失败 -1 |
order_stock_async() |
int |
请求序号 seq,结果走 on_order_stock_async_response |
cancel_order_stock() |
int |
0 成功,-1 失败(不是 True/False) |
cancel_order_stock_sysid() |
int |
同上 |
cancel_order_stock_async() |
int |
seq |
connect() / start() |
int |
0 成功 |
subscribe() / unsubscribe() |
int |
0 成功 |
query_stock_asset() |
对象 | .cash / .total_asset 等属性 |
query_stock_positions() |
list[对象] |
|
query_stock_orders() / query_stock_trades() |
list[对象] |
|
subscribe_quote() / subscribe_whole_quote() |
int |
订阅号,传给 unsubscribe_quote() |
get_full_tick() |
dict |
{code: {...}} |
get_market_data_ex() |
dict[str, DataFrame] |
MiniQMT 的 order_id 是 int(委托编号),order_sysid 是 str(柜台合同编号)。大 QMT 没有前者——get_trade_detail_data 只给 m_strOrderSysID 这个字符串。
所以这里的 order_id 是一个 int 子类,两种形态同时成立:
order_id = xt_trader.order_stock(acc, "600000.SH", 23, 100, 11, 10.0, "s", "")
isinstance(order_id, int) # True —— MiniQMT 写法照常
order_id > 0 # True
order_id == -1 # 失败时才 True
str(order_id) # '合同编号' —— 券商给的原始字符串
xt_trader.cancel_order_stock(acc, order_id) # 撤单送回的是原始字符串合同编号是纯数字时(多数券商),int 值就是那个数字,两种形态完全一致;不是纯数字时 int 是一个稳定的正数替身,而撤单、打印用的仍是真实编号。
把 order_id 存进数据库再取出来(变成普通 int)也能撤单——客户端记着最近 4096 个的对应关系。想要字符串就用 .order_sysid,它一直是 str。
同样的规则适用于 XtOrder.order_id、XtTrade.order_id,以及回调对象 XtOrderError / XtCancelError / XtOrderResponse 里的 order_id。
| 项目 | MiniQMT | 本项目 |
|---|---|---|
get_full_tick(["SH"]) |
全市场 | 默认只取股票(1.08s);要全部传 types=["all"](7.4s,含地方债等 26744 只) |
get_instrument_detail() 查不到 |
None |
{}(两者都是 falsy,if not detail 通用) |
download_history_data() |
无返回 | 返回 {"finished": n, "total": n}(多给的信息,可忽略) |
| 账户类型 | StockAccount(id, "CREDIT") 即可 |
还需服务端 BIGQMT_ACCOUNT_TYPE = "CREDIT",客户端的类型不会传到服务端 |
| 委托类型常量 | xtconstant.order_type |
内部会翻译成 passorder 的 opType(两套编号,专项两融 40–45 → 70–75) |
这些不是兼容项,是多出来的:order_stock_result()(返回完整 dict 而非单个 id)、order_stock_batch()、wait_async_orders()、ipo_subscribe_all()、sync_deployment()、get_deployment_info()、query_execution_snapshot()、local_cache_stats()。
本系统分两部分,各自需要自己的 Python 环境和依赖:
| 部分 | 运行位置 | Python | 装什么 |
|---|---|---|---|
| 客户端(外部程序) | 你的开发机 | 3.8+(推荐) | pip install xtquant-big-convert |
| 服务端(QMT 内) | QMT 的 bin.x64/python.exe |
3.6(QMT 自带) | 按传输装 1 个包 |
客户端就是写策略/调接口的那台电脑(也叫「开发机」)。直接 pip 安装:
# 基础安装(含 pyzmq,zmq 传输必需)
pip install xtquant-big-convert
# 含 redis 支持(redis 传输)
pip install xtquant-big-convert[redis]
# 含 mysql 支持(mysql 传输)
pip install xtquant-big-convert[mysql]
# 开发环境(含测试工具)
pip install xtquant-big-convert[dev]
# 从源码安装(开发模式)
git clone https://github.com/litaolemo/xtquant_big_convert.git
cd xtquant_big_convert
pip install -e .安装后可直接 import:
from bigqmt_signal_trader.xtquant_compat import configure, xt_trader, xtdata
from bigqmt_signal_trader.transports.factory import build_transport
configure()
print(xtdata.get_full_tick(["000001.SZ"]))前置:先在 QMT 界面里下载 Python 组件。 全新安装的终端
bin.x64\下没有Lib\目录,也没有python.exe——那是 Python 组件带来的,不是终端自带的,不要自己手动创建Lib\。在 QMT 客户端里下载安装该组件后,bin.x64\Lib\site-packages\才会出现,下面的路径才成立。具体入口见迅投官方文档。
QMT 自带 Python 3.6(bin.x64/python.exe),只需按你选的传输装对应依赖:
| 传输 | 服务端需要的包 | 客户端需要的包 |
|---|---|---|
| redis(默认) | redis(QMT 通常已内置) |
redis |
| zmq | pyzmq |
pyzmq(基础安装已含) |
| mysql | pymysql + DBUtils |
pymysql + DBUtils |
⚠️ 用 redis 传输就不需要装 pyzmq / pymysql / DBUtils——下面的安装说明是按需的,你用什么传输装什么。
安装到 QMT 的 Python(以 zmq / mysql 为例):
QMT 的 Python 3.6 用旧 OpenSSL,pip 直连 HTTPS 镜像会报 SSL 错误。有两种方法:
# 方法 A:从开发机拷贝纯 Python 包(推荐,绕过 SSL 问题)
# pymysql / DBUtils 是纯 Python,可直接拷贝;在开发机(已装这些包)执行:
$QMT_SITE = "D:\国金证券QMT交易端\bin.x64\Lib\site-packages"
Copy-Item -Recurse "C:\Users\<你>\anaconda3\Lib\site-packages\pymysql" "$QMT_SITE\pymysql"
Copy-Item -Recurse "C:\Users\<你>\anaconda3\Lib\site-packages\dbutils" "$QMT_SITE\dbutils"
# 方法 B:用 QMT python pip 装(可能因 SSL 失败,需配置信任)
cd D:\国金证券QMT交易端
.\bin.x64\python.exe -m pip install --trusted-host mirrors.aliyun.com pymysql DBUtils验证安装:
.\bin.x64\python.exe -c "import pymysql; from dbutils.pooled_db import PooledDB; print('OK')"pyzmq 特殊说明:包含 C 扩展,不能直接拷贝。Python 3.6 需装
pyzmq==19.0.2(最后一个支持 3.6 的版本)。如果 SSL 装不上,可下载对应 wheel 手动pip install xxx.whl。
上面 B 节表里的
redis是 redis-py 这个 Python 包(客户端库),不是 Redis 服务。用 redis 传输时你还需要一个跑着的 Redis 服务能连——服务端和客户端都连它。装完 redis-py 手里仍然没有 Redis,这一步补上。
Redis 官方从来没有 Windows 版(微软那个老 port 早归档了)。Windows 上不装 WSL 拿到 Redis,最省事的是社区重打包 redis-windows:
- 下载:https://github.com/redis-windows/redis-windows/releases —— 解压即用,无需 WSL / Linux 子系统。
- 也可以指向任何能连到的 Redis:同机、内网另一台机器、或 Docker 里的都行。Redis 服务不必和 QMT 在同一台机器。
⚠️ redis-windows 是社区重打包,不是 Redis 官方发行。是否采用由你判断;本项目只做说明,不分发它的二进制。
解压后带一份最小配置启动(redis.conf):
# 只监听本机;服务端和客户端都在这台机器时用这个
bind 127.0.0.1
port 6379
# 必设密码:见下方安全提示
requirepass 换成你自己的强密码.\redis-server.exe .\redis.conf配置里对应填法(bigqmt_signal_trader_local_config.py 和客户端配置一致):
BIGQMT_REDIS_CONFIG = {
"host": "127.0.0.1", # 跨机时填 Redis 那台的内网 IP
"port": 6379,
"db": 5,
"username": "", # redis-windows 默认无 ACL 用户,留空
"password": "换成你自己的强密码", # 与 requirepass 一致
"transport": "redis",
}🔒 务必设密码、只监听本机——这座桥的 RPC 层没有鉴权。 访问控制只有两层:Redis 自己的凭据,和
rpc_allow_order_methods开关。能连上这个 Redis、知道队列名的人就能驱动这座桥,而队列名是bigqmt:rpc:queue:<资金账号>,账号本身就在队列名里、不算秘密。rpc_allow_order_methods=True时这包括下单和撤单(默认False)。所以:
requirepass设强密码 +bind 127.0.0.1(只本机) 是最低要求,别用默认的空密码。确需跨机时——绑内网网卡、requirepass照设、并用防火墙只放行客户端那台的 IP;绝不要把这个 Redis 暴露到公网。一个空密码、bind放开、防火墙放行的 Windows Redis,挂着一个真实资金账号,是最危险的组合。
第一次部署、只想要最短路径?直接看 docs/DEPLOY_QUICKSTART.md(单账号五步跑通 + 常见问题表)。
前置:客户端已按上面「A. 客户端」装好包;服务端按「B. 服务端」装好所选传输的依赖。下面是从零跑通整套流程的步骤。
只想把配置生成出来的话,跑
bigqmt-init即可——第 3 步的两份配置它会替你写好,选单文件部署还会顺带把构建也做了。
把以下内容复制到大 QMT 的 python 目录(如 D:\国金证券QMT交易端\python\):
src/bigqmt_signal_trader/ (整个核心包,含 transports/)
src/bigqmt_signal_trader_strategy.py
src/bigqmt_signal_trader_redis_rpc_runtime.py
src/BIGQMT_REDIS_DRYRUN.py (★ Redis/MySQL/SHM 等既有 transport 的 QMT 编辑器入口)
src/BIGQMT_ZMQ_DRYRUN.py (★ 同机 ZMQ 专用入口,强制 ZMQ 并记录 bootstrap 异常)
同机 ZMQ 在 QMT“模型研究”中新建 Python 模型并加载
BIGQMT_ZMQ_DRYRUN.py;其它 transport 继续使用BIGQMT_REDIS_DRYRUN.py。ZMQ 入口只复用原入口的加载逻辑,不会创建 Redis client。纯 ZMQ 模式的能力边界:入口会关闭确实依赖 Redis 的
download_jobs(下载任务队列)和full_tick_cache(全市场快照缓存)。on_stock_order/on_stock_trade/on_order_error执行回报通过 ZMQ PUB 推送,MiniQMT 风格回调可以正常使用,但没有 Redis Stream 的短时回放能力。行情查询、下单/撤单、持仓查询等 RPC 全部正常。
在 QMT 的 python 目录创建 bigqmt_signal_trader_local_config.py(不要提交此文件):
# coding: utf-8
BIGQMT_ACCOUNT_ID = "你的资金账号" # 如 "1234567890"
BIGQMT_REDIS_CONFIG = {
"host": "你的Redis地址", # 如 "192.168.1.100"
"port": 6379,
"db": 5,
"password": "你的Redis密码",
# === 传输选择(默认 redis,生产推荐)===
# "transport": "redis", # 不写就是 redis
# 切 zmq:装了 pyzmq 后只需这一行。端口按账号派生 127.0.0.1:1556x。
# 注意 zmq 实测比 redis 慢(ping 95ms vs 10ms,交易查询 95ms vs 4ms),
# 它的用途是「这台机器没有 redis」,不是低延迟。
# "transport": "zmq",
# 切 mysql(兼容兜底):需装 pymysql+DBUtils,同样自动开 background_threads。
# "transport": "mysql",
# "mysql": {"driver":"pymysql","host":"...","port":3306,"user":"root",
# "password":"...","database":"bigqmt_rpc","charset":"utf8mb4"},
"rpc_allow_order_methods": False, # 下单默认关闭
"rpc_process_in_listener": True, # 只读请求在收包线程直接处理(低延迟)
"rpc_listener_methods": ("*",), # * = 所有只读方法
"rpc_background_threads": False, # redis 用 QMT adjust 线程 drain
"schedule_adjust": True,
"schedule_adjust_interval": "500nMilliSecond",
}
rpc_background_threads保持False,包括 zmq 和 mysql。0.3.21 起这两种传输 也支持 adjust 线程 drain(#183),实测把 zmq 的 ping 从 405ms 降到 95ms、交易查询从 607ms 降到 95ms。0.3.21 之前它们必须设True,现在设True等于主动放弃这段提速。 不写这个键则沿用历史默认(开后台线程)。
同机 ZMQ 使用 src/BIGQMT_ZMQ_DRYRUN.py,其它 transport 使用 src/BIGQMT_REDIS_DRYRUN.py。两者都是 QMT 编辑器入口;ZMQ 入口会在正常 logger 初始化前失败时把 traceback 写入 <QMT python>\logs\bigqmt-bootstrap-error.log。部分券商 QMT 缺少标准 importlib 时,统一入口会注册仅包含 import_module/reload 的最小兼容模块。
它是 QMT 编辑器入口的"外壳"(shell),按顺序做 5 件事:
- 定位 python 目录:把 QMT 的
python目录加到sys.path,让bigqmt_signal_trader包能 import。 - reload 模块:
importlib.reload刷新redis_common/redis_rpc/strategy/runtime—— QMT 在编辑器里重跑策略时,进程不退出,reload 确保新代码立即生效。 - 注入 Redis 配置:读
bigqmt_signal_trader_local_config.py里的BIGQMT_REDIS_CONFIG,调configure_runtime_redis()。 - 注入账号:读
BIGQMT_ACCOUNT_ID,调configure_runtime_account()。如果配置没给,fallback 用 QMT 全局变量account。 - 绑定 QMT 原生 API:把 QMT 内置的
passorder/cancel/get_trade_detail_data函数绑进 runtime(用try/except NameError包住,因为这些名字只在大 QMT 进程内存在)。 - 导出 QMT 回调:
init = _runtime.init/handlebar = _runtime.handlebar/adjust = _runtime.adjust等,让 QMT 能回调到我们的策略逻辑。
BIGQMT_REDIS_DRYRUN.py 里有一处写死的 QMT python 目录路径,作为 __file__ 找不到时的 fallback:
def _known_qmt_python_dir():
root = "".join(chr(value) for value in (0x56fd, 0x91d1, 0x8bc1, 0x5238)) # 国金证券
suffix = "".join(chr(value) for value in (0x4ea4, 0x6613, 0x7aef)) # 交易端
return "D:\\" + root + "QMT" + suffix + "\\python"
# 解码后 = D:\国金证券QMT交易端\pythonchr()编码是为了规避 QMT 用 GBK 保存策略文件时中文乱码(用 Unicode 码点拼出"国金证券交易端")。- 路径优先级:先用
__file__所在目录(脚本实际位置),找不到才用这个硬编码 fallback。 - 如果你的 QMT 装在别的路径(比如
D:\华泰QMT\python):通常不用改,因为__file__优先。但如果你用exec方式加载(__file__未定义),需要把_known_qmt_python_dir()改成你的路径,或直接硬编码:def _known_qmt_python_dir(): return r"D:\你的券商QMT\python"
[bigqmt_shell] reload entry paths=['D:\\国金证券QMT交易端\\python']
[bigqmt_shell] local redis config loaded keys=['host', 'port', 'db', ...]
[bigqmt_shell] local account config loaded=True
[bigqmt_rpc] transport=redis mode process_in_listener=True listener_methods=('*',) ...
[bigqmt_rpc] started channel=bigqmt:rpc:req:你的账号
[bigqmt_signal_trader] init ok
为什么是 GBK 编码? QMT 的策略编辑器用本地代码页(中文 Windows 是 GBK)保存文件。文件头
#coding:gbk声明编码,避免 QMT 保存时破坏 UTF-8 内容。源码本身是 ASCII(中文用chr()拼),所以实际不会乱码。
为什么不直接用
bigqmt_signal_trader_redis_rpc_runtime.py? 那个文件是纯逻辑入口,不包含 reload 和 QMT API 绑定。QMT 编辑器应加载与 transport 对应的外壳:同机 ZMQ 使用BIGQMT_ZMQ_DRYRUN.py,其它 transport 使用BIGQMT_REDIS_DRYRUN.py;不要直接加载 runtime 文件。
方式 A:用兼容层(推荐,旧代码零改动)
客户端创建配置文件 bigqmt_signal_trader_client_config.py(与上面类似但用客户端视角),然后:
from bigqmt_signal_trader.xtquant_compat import StockAccount, configure, xt_trader, xtdata
configure()
acc = StockAccount(xt_trader.client.account_id, "STOCK")
# 行情
ticks = xtdata.get_full_tick(["000001.SZ"])
print(ticks["000001.SZ"]["lastPrice"])
# 持仓 / 资金
positions = xt_trader.query_stock_positions(acc)
asset = xt_trader.query_stock_asset(acc)
print(asset.cash, asset.total_asset)
# K线(自动还原成 pandas DataFrame)
klines = xtdata.get_market_data_ex(
field_list=["close"], stock_list=["000001.SZ"], period="1d", count=5
)方式 B:直接 RPC 调用
from bigqmt_signal_trader.redis_rpc import call_redis_rpc
import redis
r = redis.Redis(host="192.168.1.100", port=6379, db=5, password="...")
resp = call_redis_rpc(r, "你的账号", "get_full_tick", {"codes": ["000001.SZ"]})
print(resp["data"]["000001.SZ"]["lastPrice"])方式 C:无缝替换旧 xtquant(最终切换)
把仓库 src 放到 PYTHONPATH 最前面,旧代码的 from xtquant import xtdata 自动命中本仓库 shim:
$env:PYTHONPATH = "D:\gjzqqmt\xtquant_big_convert\src;$env:PYTHONPATH"# 旧代码完全不改
from xtquant import xtdata
ticks = xtdata.get_full_tick(["600000.SH"]) # 走 RPC 到大 QMT服务端 + 客户端的配置文件里,transport 字段保持一致即可:
BIGQMT_REDIS_CONFIG = {
"transport": "zmq", # redis / zmq / mysql / shm
"zmq": {"host": "127.0.0.1"}, # 各传输子配置
# redis 配置保留(zmq 服务发现、mysql 不需要时的 fallback 都用它)
}Redis(默认):
{"transport": "redis"} # 或省略 transport 字段ZMQ(无 redis 时的同机方案,需 pyzmq):
{
"transport": "zmq",
"rpc_background_threads": False, # 0.3.21 起走 adjust drain,快 4~6 倍(#183)
"zmq": {
"host": "127.0.0.1", # 默认端口从 account_id 派生
# "port": 5560, # 可显式指定
# 端口冲突时自动找空闲端口 + 通过 Redis 服务发现告知客户端
},
}MySQL(兼容兜底,需 pymysql + DBUtils):
{
"transport": "mysql",
"rpc_background_threads": False, # 0.3.21 起走 adjust drain,快 4~6 倍(#183)
"mysql": {
"driver": "pymysql",
"host": "192.168.1.100", "port": 3306,
"user": "root", "password": "...",
"database": "bigqmt_rpc", "charset": "utf8mb4",
"poll_interval_seconds": 0.01,
"pool_config": {"mincached": 1, "maxcached": 3, "maxshared": 0, "maxconnections": 4},
},
}- 默认端口从 account_id 派生:
15560 + (账号数字 mod 100),不同账号自动不冲突。 - 端口被占时,server 自动往上扫描找空闲端口,把真实地址写到 Redis key
bigqmt:zmq:addr:{account_id}(TTL 300s)。 - 客户端连接时按优先级解析地址:显式
connect_address> Redis 服务发现 > 默认派生端口。 - server 退出时自动清理 discovery key。
- 服务发现是可选的(没配 Redis client 时退化为静态派生端口)。
完整传输层文档见 docs/RPC_TRANSPORTS.md。
三种传输全部实测,端到端连接真实 QMT 进程,n=15/方法:
| 传输 | ping p50 | ping p90 | 交易查询 p50 | 串行吞吐(ping / 交易查询) |
|---|---|---|---|---|
| Redis | 10ms | 105ms | 4ms | 20 / 195 次每秒 |
| ZMQ(drain,#183) | 95ms | 110ms | 95ms | 10 / 10 次每秒 |
| ZMQ(后台线程,0.3.21 前的默认) | 405ms | 408ms | 607ms | 2.4 / 1.7 次每秒 |
| MySQL | ~104ms | — | — | — |
生产推荐 Redis,而且它就是实测最快的那个 —— 早期版本说「ZMQ 理论最快」是错的。
ZMQ 的 drain 模式被钉在一个 adjust tick(95ms ≈ 100ms tick),因为它每 tick 只轮询
一次;Redis 的阻塞 brpop 是请求一落队列就推回来,不等 tick。并发也只有 Redis 有
用:ZMQ 客户端整个请求周期持单 socket 锁(#186),4 并发和串行一样快。
ZMQ 的用途是「这台机器没有 redis」。MySQL 仅作兜底。
复现基准:
python bench_latency.py # Redis 单传输延迟
python bench_transports.py -n 100 # Redis vs ZMQ 对比src/bigqmt_signal_trader/
├── transports/ 可插拔传输层
│ ├── base.py RpcTransport 抽象接口
│ ├── redis_transport.py Redis(默认,rpush/blpop/brpop)
│ ├── zmq_transport.py ZMQ(ROUTER/DEALER + 服务发现)
│ ├── mysql_transport.py MySQL(轮询 + DBUtils 连接池)
│ ├── shm_transport.py 共享内存(stub)
│ └── factory.py build_transport 工厂
├── adapters/ QMT API 适配器
│ ├── market_bigqmt.py 行情(ContextInfo 封装)
│ ├── order_bigqmt.py 下单(passorder)
│ ├── position_bigqmt.py 持仓(get_trade_detail_data)
│ └── redis_common.py Redis 连接/编解码
├── redis_rpc.py RPC 服务(handlers + service + transport 集成)
├── xtquant_compat.py 客户端兼容层(xt_trader / xtdata + 异步回调)
├── exec_events.py 委托/成交/错误事件推送(Redis pubsub)
├── quote_push_channel.py 全推行情推送通道(redis/zmq PUB/SUB)
├── quote_subscription_manager.py 服务端全推订阅管理(引用计数 + 组合键去重)
├── whole_quote_session.py 客户端全推订阅会话(心跳 + 重启恢复)
├── full_tick_cache.py 全市场行情快照缓存(可选降载)
├── strategy.py 之类 策略骨架、风控、价格引擎等
bigqmt_no_redis/ 无 redis 版本(QMT 沙箱拒绝 import redis 时用)
│ ├── zmq_transport.py 自包含 ZMQ transport(内联编码,零 redis 依赖)
│ └── DRYRUN_no_redis.py 无 redis DRYRUN 入口
src/xtquant/ 可选 xtquant import shim
src/bigqmt_signal_trader_strategy.py 策略入口(init/handlebar/adjust + 启动诊断)
src/bigqmt_signal_trader_redis_rpc_runtime.py Redis RPC runtime 入口
src/BIGQMT_REDIS_DRYRUN.py QMT 编辑器加载入口(GBK)
src/BIGQMT_ZMQ_DRYRUN.py 同机 ZMQ QMT 编辑器入口(GBK)
src/BIGQMT_ZMQ_BACKTEST.py 独立 QMT 回测 ZMQ 入口(GBK)
src/bigqmt_backtest/ 独立历史驱动、模拟撮合、ZMQ 协议与客户端
tests/bigqmt_signal_trader/ 单元测试(无 QMT 环境可跑)
tests/bigqmt_backtest/ 回测、确定性、隔离和 ZMQ 往返测试
qmt-trader/ AI 助手 Skill(大模型直接操作 QMT,见下文专节)
│ ├── SKILL.md skill 说明书(命令速查 + 工作流 + 安全须知)
│ ├── scripts/qmt.py 统一 CLI(47 子命令 + rpc 兜底)
│ └── references/api_reference.md 完整 API 参考
docs/ 详细文档
test_all_apis.py 端到端 API 测试(发现生产问题)
bench_latency.py / bench_transports.py 延迟基准脚本
python -m pytest tests/bigqmt_signal_trader/ -q当前覆盖 199 个用例(含传输层往返、Redis RPC、客户端兼容、持仓/行情/下单 handlers、异步回调、执行事件)。
test_all_apis.py 是端到端验证测试——不只测「调用成功」,还测「结果正确」,能发现这些生产问题:
| 验证项 | 检测什么 | 为什么重要 |
|---|---|---|
| 客户端/服务端一致性 | ping 超时 → transport 不匹配 | Issue #24 根因:客户端 redis / 服务端 zmq 连不上 |
| 持仓查询 | get_positions 返回空但账户有持仓 |
容错设计把「失败返回空」当成「正常」 |
| 委托查询 | query_orders 返回空 |
strategy_name 不匹配(默认应为 "" 返回全部) |
| 买入/卖出 | submit_order 成功但委托没进系统 |
静默失败(passorder 被 QMT 拒绝但没报错) |
| server_error | 显示 QMT 端拒绝原因 | 委托被 QMT 静默拒绝时返回具体原因 |
用法:
# 方式 A:用环境变量
$env:BIGQMT_ACCOUNT_ID="你的账号"
$env:BIGQMT_REDIS_HOST="你的Redis地址"
$env:BIGQMT_REDIS_PORT="6379"
$env:BIGQMT_REDIS_DB="5"
$env:BIGQMT_REDIS_PASSWORD="你的密码"
python test_all_apis.py
# 方式 B:用 QMT 端配置(需 bigqmt_signal_trader_local_config.py 在 PYTHONPATH)
$env:PYTHONPATH="D:\国金证券QMT交易端\python;$env:PYTHONPATH"
python test_all_apis.py示例输出(发现问题时):
--- 端到端验证: 客户端/服务端一致性 ---
客户端配置 transport: redis
❌ ping 失败: redis rpc timeout: ping
可能原因: 客户端 transport 和服务端不匹配
- 客户端配置 transport=redis
- 如果服务端是 zmq, 客户端也要设 transport=zmq
--- 端到端验证: 持仓查询 ---
⚠️ get_positions 返回空 — 账户可能真的没持仓, 或查询失败 (检查 QMT 上下文)
--- 端到端验证: 买入/卖出 ---
✅ submit_order OK
❌ 委托没进系统 — submit_order 成功但 query_orders 找不到
这是静默失败 (passorder 被 QMT 拒绝但没报错)
检查: 1) 价格是否超出范围 2) 账户权限 3) QMT 风控
系统自带文件日志——所有报错/异常同时写 QMT 输出面板和本地日志文件,重启/崩溃后也能回溯。
下单失败有四种完全不同的原因,报错长得不一样,别混:
| 你看到的报错 | 原因 | 怎么修 |
|---|---|---|
ValueError: rpc method is not allowed: order_stock |
rpc_allow_order_methods 是 False(默认值),下单方法根本没进服务端白名单 |
服务端配置改 True,重启策略 |
RuntimeError: passorder is not available in Big QMT runtime |
QMT 没注入 API 全局 —— 这个文件被当成普通脚本执行了 | 加到模型交易里运行,别在策略编辑器窗口点运行;检查没勾「独立 python 进程」 |
server_error: passorder submitted but order not found in system |
委托没进系统。最常见是 QMT 模型交易的运行模式是「模拟」(默认值)—— passorder 内部撮合,永远到不了券商 |
运行模式改实盘 |
order_gateway is not configured |
策略 init 挂了 |
看启动日志找真正的异常 |
最常见的是第一条。 一句话确认:
xt_trader.client.call("ping")["allow_order_methods"]
# False -> 就是它服务端 bigqmt_signal_trader_local_config.py:
BIGQMT_REDIS_CONFIG = {
# ...
"rpc_allow_order_methods": True, # 默认 False
}改完必须重启策略,
reload_deployment()刷不了顶层的bigqmt_signal_trader_strategy.py。
这个默认值是有意保守的(见下文「安全默认值」):任何能连上这条通道的程序都能下单,所以要显式打开。
| 环境 | 日志文件 |
|---|---|
| QMT 内(服务端) | <QMT python 目录>\logs\bigqmt.log(如 D:\国金证券QMT交易端_lemo\python\logs\bigqmt.log) |
| 外部客户端 | ~\.cache\bigqmt\logs\bigqmt.log(用户目录下) |
- 按天轮转(午夜),默认保留最近 7 天。
- 每行带时间戳 + 级别 + 模块标签:
2026-08-14 21:45:59 [ERROR] [bigqmt.quote_push] publisher start failed: ...
# 实时跟踪日志
Get-Content "D:\国金证券QMT交易端\lempython\logs\bigqmt.log" -Wait -Tail 50
# 只看错误
Get-Content "D:\...\python\logs\bigqmt.log" | Select-String "ERROR|WARN"| 环境变量 | 默认 | 说明 |
|---|---|---|
BIGQMT_LOG_ENABLED |
1 |
置 0 关闭文件日志 |
BIGQMT_LOG_TO_STDOUT |
1 |
置 0 不输出到 QMT 面板 |
BIGQMT_LOG_RETENTION_DAYS |
7 |
日志保留天数 |
排错首选看日志文件:QMT 面板内容重启/清空后丢失,日志文件保留 7 天,包含启动诊断(
[bigqmt_diag])、崩溃原因、端口冲突等。
rpc_allow_order_methods默认False:远程order_stock/cancel_order被拒绝。确认接入方、账号、风控后再显式开启。- 回测桥接永久
live_ready=false,协议中没有真实账户和实盘下单方法。 - 配置文件含资金账号和密码,
bigqmt_signal_trader_local_config.py/bigqmt_signal_trader_client_config.py已在.gitignore,不要提交。 - 请求负载经过 base64 + 数字混淆编码(
encode_rpc_request_payload),避免 QMT 的 Redis 客户端拦截含股票代码的明文。
仓库内置一个 Agent Skill——qmt-trader/,让支持 SKILL.md 约定的 AI 编程助手(Claude Code / ZCode / Cursor / Codex 等)直接用命令行驱动 QMT 的全部交易与行情能力,无需每次现场写 Python 调用代码。人也可以脱离 AI 手动执行其中的 CLI 脚本。
qmt-trader/
├── SKILL.md skill 说明书(触发条件 + 命令速查 + 典型工作流 + 安全须知)
├── scripts/qmt.py 统一 CLI 入口(47 个子命令 + 通用 rpc 兜底,约 1000 行)
└── references/api_reference.md 完整 API 参考(参数/返回值/常量/已知陷阱)
- AI 助手匹配到
SKILL.md里的description("查行情 / 查持仓 / 下单 / 龙虎榜 / 北向资金…时触发")后自动加载本 skill; - 之后助手调用
python qmt-trader/scripts/qmt.py <子命令>执行确定性命令,不再临时生成 RPC 调用代码,避免参数写错; - 所有命令默认输出 JSON(
ok/data/ts三字段,便于模型解析),加--table切换人类可读表格;出错时返回ok: false+error/detail/code,退出码 1; qmt.py自动把仓库src/加入sys.path(开发模式免 pip install),并自动发现 QMT 的 python 目录读取客户端配置。
方式 A:安装到 AI 助手的 skills 目录(推荐,全局生效):
# Claude Code
cp -r qmt-trader ~/.claude/skills/qmt-trader
# ZCode / 其他遵循 agents skills 约定的助手
cp -r qmt-trader ~/.agents/skills/qmt-trader安装后正常提需求即可,例如"帮我看下工商银行最近的走势""我账户现在什么持仓",助手会自动触发。
方式 B:不安装,对话里显式指定:
阅读 qmt-trader/SKILL.md,之后用里面的 qmt.py 命令帮我查行情 / 持仓 / 下单。
方式 C:纯手动(不经过 AI,人直接当 CLI 用):
python qmt-trader/scripts/qmt.py ping
python qmt-trader/scripts/qmt.py snapshot --table与「快速开始」的客户端一致:
- QMT 端 RPC 服务已启动(同机 ZMQ 运行
BIGQMT_ZMQ_DRYRUN.py,其它 transport 运行BIGQMT_REDIS_DRYRUN.py,输出面板/日志看到启动诊断 OK); - 客户端配置就绪——环境变量(
BIGQMT_ACCOUNT_ID/BIGQMT_REDIS_HOST/BIGQMT_REDIS_PORT/BIGQMT_REDIS_DB/BIGQMT_REDIS_PASSWORD)或配置文件; - 先
ping确认连通:redis 约 10ms / zmq 约 95ms 为正常,超时说明 transport 或配置不匹配。
# 0. 连通性检测(含延迟测量)
python qmt-trader/scripts/qmt.py ping
# 1. 账户全景:资产 + 持仓 + 委托 + 成交(一次往返)
python qmt-trader/scripts/qmt.py snapshot
# 2. 实时五档盘口(含涨跌幅)
python qmt-trader/scripts/qmt.py tick 600000.SH
# 3. 前复权日 K 60 根(含 MA5/20/60 统计)
python qmt-trader/scripts/qmt.py kline 600000.SH --period 1d --count 60 --dividend front
# 4. 干跑下单(只打印不提交,确认参数)
python qmt-trader/scripts/qmt.py buy 600000.SH 100 --price 7.50 --dry-run| 分类 | 命令 |
|---|---|
| 连通/全景 | ping / snapshot |
| 账户 | account(资产)/ positions(持仓含浮动盈亏)/ orders(委托含语义化状态)/ trades(成交) |
| 行情 | tick / kline / instrument / sector / trading-dates / north(北向)/ longhubang(龙虎榜)/ financial(财务)/ download(历史数据下载)/ quote-subscribe(全推订阅) |
| 期权分析 | option-greeks <option_code>(单合约)/ option-greeks 510050.SH --expiry 202609(整条链,本地 IV + Delta/Gamma/Vega/Theta/Rho) |
| 扩展查询(25 个快捷命令) | holiday / stock-name / instrument-type / divid-factors / market-times / trading-calendar / option-list / bsm-price / bsm-iv / hkt-stats / hkt-details / hkt-rate / top10-holder / holder-num / ipo / ipo-limit / credit-assure / credit-short / credit-debt / his-st / index-weight / industry / sector-info / local-data / timetag2dt / dt2timetag |
| 交易 | buy / sell / cancel(均支持 --dry-run,buy/sell 支持 --latest / --strategy / --remark) |
| 通用兜底 | rpc <method> [json] — 调用白名单内任意方法(如 rpc get_l2_quote '{"stock_code":"600000.SH"}'),未列出的方法都能这样调 |
- 下单三命令(
buy/sell/cancel)受服务端白名单控制,rpc_allow_order_methods默认False,未显式开启时返回ORDER_DISABLED; - 下单前先用
tick看价 +--dry-run确认参数; - 报
ORDER_TIMEOUT时不要直接重试,先orders查询确认委托是否已进系统,避免重复下单; - 下单的
--strategy与查询的--strategy需一致;查全部委托用orders --strategy ""(空 = 不过滤)。
完整命令表、四个典型工作流(行情分析 / 持仓监控 / 下单交易 / 批量分析)和 API 参数细节见 qmt-trader/SKILL.md 与 qmt-trader/references/api_reference.md。
bigqmt-dashboard —— 大QMT 直连的多账号持仓监控与下单面板。浏览器里看持仓、资金曲线、买卖流水,点一下就把单子报进大QMT。
它是本项目目前最完整的下游使用者,几乎把这里的接口都跑了一遍——如果你想知道某个 API 在真实业务里怎么用,那边有现成的代码:
| 它用了什么 | 对应到本项目 |
|---|---|
| 每账号独立连接、可连不同机器上的大QMT | 直接构造 BigQmtXtTrader(account_id=..., redis_config=...),不用 configure() 的模块级单例 |
| 账户数据同步 | query_stock_positions / query_stock_asset / query_execution_snapshot |
| 实时委托与成交回报 | register_callback + start(),回报经 exec_events 推来 |
| 下单撤单 | order_stock_result / cancel_order_stock(需 rpc_allow_order_methods=True) |
| 实时行情与分钟线 | get_full_tick / get_market_data_ex(缺数据时先 download_history_data2 再重试) |
| 合约属性 | get_instrument_detail / get_instrument_type,走 FormulaServer 直连快速路径 |
| 打新债 | ipo_subscribe_all(stock_type="BOND") |
| 换传输不改代码 | 账号配置里的 rpc 段整包透传给 BigQmtRpcClient,transport 改 redis/zmq 即可 |
几个从对接中反馈回来、值得单独提一句的点:
- 可转债的下单规整要自己写。
code_utils.min_lot()只认「688 开头 = 200,其余 = 100」,可转债最小 10 张会被(10 // 100) * 100规整成 0;normalize_stock_code()对裸 6 位码按「5/6 开头 = 沪市」判断,沪市转债110xxx会被判到深市。面板那边重写了一份全品种规则(含科创板 200 股起按 1 股递增、ETF/转债 0.001 报价精度),并拿get_instrument_detail返回的PriceTick交叉验证过 9 个品种,全部吻合。 get_market_data_ex读的是 QMT 本地库。 没download_history_data2过的标的返回 0 根而不是报错——面板实测 10 只持仓全都没有 1m 数据,走势图整列是空的,加了「缺数据先下载再重试」才好。docs/XTQUANT_COMPAT_REPLACEMENT.md里「RPC 暂不推送回调」是旧文。 代码里BigQmtXtTrader.start()会拉起执行事件监听线程,on_stock_order/on_stock_trade是真的会触发的。
- CHANGELOG.md — 版本变更记录(新增/修复/变更)
- docs/DEPLOY_QUICKSTART.md — 单账号部署快速开始(最短路径 + 部署期常见问题表)
- docs/LATENCY_REPORT.md — 延迟测试报告(传输层对比、FormulaServer 直连、下单链路、方法论)
- docs/RPC_API_REFERENCE.md — 全部 RPC 方法参考(参数、返回值、别名、大 QMT 能力边界)
- docs/FORMULA_SERVER_FASTPATH.md — FormulaServer(58600) 直连快速路径:协议、映射表、能力边界与回退行为
- docs/SUBSCRIBE_WHOLE_QUOTE_PUSH.md — 全推行情订阅推送机制设计
- docs/SUBSCRIBE_WHOLE_QUOTE_LIVE_VERIFICATION.md — 全推行情实盘验证报告
- docs/BIG_QMT_REDIS_RPC.md — Redis RPC 协议与入口脚本详解
- docs/RPC_TRANSPORTS.md — 可插拔传输层完整说明
- docs/XTQUANT_COMPAT_REPLACEMENT.md — 用兼容层替换旧 xtquant 的步骤
- docs/BIG_QMT_SIGNAL_TRADER_RUNBOOK.md — 信号交易运行手册
- docs/ZMQ_BACKTEST_BRIDGE.md — 独立 ZMQ 回测协议、撮合规则和 QMT 入口
- qmt-trader/ — QMT Trader skill:AI 助手统一 CLI 驱动全部 QMT API(47 子命令 + 通用 rpc 兜底),用法见上文「AI 助手 Skill:qmt-trader」
- bigqmt-dashboard — 基于本项目的持仓监控与下单面板:多账号、服务端风控闸门、完整可转债支持,可当作接口的实际用法参考专节
官方 xtquant.xttrader.XtQuantTrader 依赖客户端侧 XtQuantServer 通道。当前国金大 QMT 环境中直接连 connect() 返回 -1,交易能力因此必须放在大 QMT 内部策略进程里,外部通过 RPC 驱动。
但只读行情不必走 RPC。 58600 是 FormulaServer,它同时就是行情/参考数据服务——QMT 自带 Python 里的 qmt_api 包(bin.x64/Lib/site-packages/qmt_api)正是它的客户端。本仓库已接入这条直连快速路径,见上文「FormulaServer 直连快速路径」。
如果后续券商开通 XtQuantServer 权限且 connect()==0,可再加交易直连模式。


