Architecture
本地 Web 到 QMT 的交易与行情桥接
统一处理鉴权、账号路由、行情订阅和交易请求,让外部程序通过稳定接口接入 QMT 能力。
外部程序
cfquant Web
Socket / Pipe
QMT 终端
项目在本机启动 Web 服务,外部 Python 默认先通过 LTtx 发现 Web 路由,网页请求直接进入 Web;两者最终都由账号配置自动路由到通用端或高级端。新用户优先使用通用模式,一个 QMT 加载一个 ctypes 文件即可完成资金、持仓、委托、下单、撤单和回调验证。
| 账号 | 首选模式 | 实际模式 | QMT 目录 | 数据源 |
|---|
| 代码 | 名称 | 持仓 | 可用 | 成本价 | 市值 | 盈亏 |
|---|
| 序号 | 最后回调 |
|---|
| 代码 | 名称 | 持仓 | 可用 | 成本价 | 市值 | 盈亏 |
|---|
| 序号 | 最后回调 |
|---|
| 时间 | 代码 | 名称 | 价格 | 数量 | 金额 |
|---|
| 操作 | 账号名称 | 资金账号 | 连接状态 | 首选模式 | 实际模式 | 内部通道 | QMT 目录 | 数据源 |
|---|
| 代码 | 名称 | 持仓 | 可用 | 市值 |
|---|
| 接收时间 | 事件 | 账号 | 代码 | 委托 / 成交编号 | 价格 | 数量 | 成交量 | 状态 / 摘要 | 来源 | 完整信息 |
|---|
--
正在读取测试脚本...
本地服务日志统一写入项目 log/ 目录,默认自动保留最近 30 天;根目录旧日志也会纳入过期清理。
选择后,PipeHub、LTtx、更新安装和重启流程都会使用该解释器。
查看当前 Web 服务加载的版本、安装目录和启动脚本位置。
一次更新完整版本,并把最新 cfquant 核心同步到所有已绑定 QMT 目录。
填写账号、选择模式并保存,系统自动同步项目代码和 QMT 内部策略代码,然后按提示启动并登录 QMT。
bin.x64;无需手工复制代码,只需先完成 QMT Python 库下载。
bin.x64。
高级模式
需要两个 QMT,普通端和极速交易端都在线后再使用。
排查
先看绑定状态和 QMT 日志,再看 PipeHub 是否在线。
cfquant 是本机 QMT 桥接控制台。它把 Web 页面、外部 Python 和大 QMT 策略脚本连在一起,由 Web 统一管理账号、运行模式、回调和更新。
| 模式 | QMT 入口 | 适合场景 |
|---|---|---|
| 通用模式 | CFQUANT_CTYPE_ALL_LOWLAT.py |
默认推荐。一个 QMT 即可跑通行情、查询、交易和回调。 |
| 极致模式 | CFQUANT_LITE.py |
入口自包含,适合国泰君安、国泰海通等导入受限环境。 |
| 高级模式 | CFQUANT.py + CFQUANT_TRADE_LOWLAT.py |
两个 QMT,普通端做查询和回调,极速交易端做低延迟交易请求。 |
| 位置 | 作用 |
|---|---|
runtime/config/cfquant_web_config.json |
保存账号、QMT 目录、运行模式、共享行情源和市场路由。 |
qmt_scripts/ |
放给 QMT 加载的入口脚本。 |
bin.x64/cfquant_bridge_config.json |
Web 为 QMT 写入的身份文件,用于区分不同 QMT 终端。 |
log/ |
Web、PipeHub、LTtx 和 QMT 桥接日志。 |
start_cfquant.bat,或执行 cfquant --open-browser。bin.x64 目录和运行模式。网页端负责配置、验证和排查。外部策略接入前,先用网页确认账号和 QMT 链路是通的。
这里的“原来”指 MiniQMT / 原版 xtquant,“现在”指外部 Python 通过 cfquant 接入大 QMT。示例运行在 PyCharm、VS Code 或命令行的外部 Python 环境中;QMT 内部运行的是“部署教程”中的桥接入口脚本。
YOUR_ACCOUNT_ID 换成绑定的资金账号,保留字符串引号。python -m pip install --upgrade cfquant # 需要 ZMQ 能力时安装:python -m pip install "cfquant[zmq]" # 使用本地源码时,在项目根目录执行:python -m pip install -e . python -c "import cfquant; print(cfquant.__version__, cfquant.__file__)"
cfquant --open-browser cfquant qmt-scripts --output D:\QMT\cfquant
以上命令用于首次准备;Web 和 QMT 入口已运行时,直接执行策略即可。安装包和导出脚本不会自动完成 QMT 侧的部署。
常用接口保留了接近 xtquant 的名称与参数。迁移先调整导入和连接初始化,再逐项核对策略实际用到的接口;同名入口不代表所有返回字段、异步时序和券商能力都完全一致。
| 位置 | 原来:xtquant / MiniQMT | 现在:cfquant / 大 QMT |
|---|---|---|
| 导入模块 | from xtquant import xtdata, xtconstant | from cfquant import xtdata, xtconstant;交易类、账号类和回调类的导入前缀一起替换。 |
| 交易连接 | XtQuantTrader(path, session_id),path 指向 MiniQMT 的 userdata_mini。 | XtQuantTrader("", session_id);QMT 目录由 Web 账号绑定管理,构造函数中的 path 只作兼容保留。 |
| 会话标识 | 传入用于区分连接的 session_id。 | 可继续传正整数;省略或传 0 时自动生成正整数,可读取 trader.session_id。 |
| 账号对象 | StockAccount("YOUR_ACCOUNT_ID", "STOCK") | 相同写法;资金账号和账户类型必须与 Web 绑定一致,账号不能写成整数。 |
| 启动与订阅 | start → connect → subscribe | 可保留这个顺序。若构造时传 account=account,start() 会尝试自动订阅;下文用显式订阅检查结果。 |
| 行情与查询 | get_full_tick、query_stock_asset、query_stock_positions 等。 | 常用调用参数可沿用;数据来自当前大 QMT 桥接链路。 |
| 下单与回调 | order_stock、cancel_order_stock、XtQuantTraderCallback。 | 沿用常用签名和回调名;返回请求结果后仍需核对委托、成交或错误回报。 |
from xtquant import xtdata, xtconstant from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback from xtquant.xttype import StockAccount
from cfquant import xtdata, xtconstant from cfquant.xttrader import XtQuantTrader, XtQuantTraderCallback from cfquant.xttype import StockAccount
同一份策略里的交易类、常量、账号和行情模块应一起迁移,避免一部分请求仍然发往原版 MiniQMT。
这两个示例展示同一条查询流程。当前示例可单独保存为 query_account.py 后运行,只查询数据,不提交订单。
import time
from xtquant.xttrader import XtQuantTrader
from xtquant.xttype import StockAccount
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
trader = XtQuantTrader(r"D:\QMT\userdata_mini", int(time.time()))
try:
trader.start()
if trader.connect() != 0:
raise RuntimeError("MiniQMT connection failed")
if trader.subscribe(account) != 0:
raise RuntimeError("Account subscription failed")
print(trader.query_stock_asset(account))
print(trader.query_stock_positions(account))
print(trader.query_stock_orders(account))
print(trader.query_stock_trades(account))
finally:
trader.stop()
from cfquant.xttrader import XtQuantTrader
from cfquant.xttype import StockAccount
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
trader = XtQuantTrader("", 0)
try:
trader.start()
if trader.connect() != 0:
raise RuntimeError("CFQuant connection failed; check Web and QMT")
if trader.subscribe(account) != 0:
raise RuntimeError("Account subscription failed; check binding")
print("session_id:", trader.session_id)
asset = trader.query_stock_asset(account)
if asset is None:
print("No asset data returned")
else:
print("account:", asset.account_id)
print("cash:", asset.cash, "total_asset:", asset.total_asset)
positions = trader.query_stock_positions(account)
for position in positions:
print(position.stock_code, position.volume, position.can_use_volume)
print("position_count:", len(positions))
for order in trader.query_stock_orders(account):
print("order:", order.order_id, order.stock_code,
order.order_status, order.traded_volume, order.order_remark)
for trade in trader.query_stock_trades(account):
print("trade:", trade.order_id, trade.stock_code,
trade.traded_volume, trade.traded_price)
finally:
trader.stop()
query_stock_asset() 返回资产对象;持仓、委托和成交查询返回对象列表,可用点号读取字段。空持仓或当日没有委托时列表可以为空。connect() == 0 说明连接检查通过,仍需用实际账号查询确认绑定和 QMT 数据可用。脚本结束时调用 stop() 释放本进程的交易订阅和连接。
仅查询行情时无需创建 XtQuantTrader。证券代码仍带市场后缀,例如 000001.SZ、600000.SH;先确认网页中的行情源已在线。
from xtquant import xtdata ticks = xtdata.get_full_tick(["000001.SZ", "600000.SH"]) print(ticks)
from cfquant import xtdata
ticks = xtdata.get_full_tick(["000001.SZ", "600000.SH"])
for code, tick in ticks.items():
print(code, tick.get("lastPrice"), tick.get("volume"))
原来的 download_history_data() 和 get_market_data_ex() 常用参数可保留,导入前缀改为 cfquant。下面读取固定日期范围的日线;日期用于演示历史查询,可按需要调整。
from cfquant import xtdata
code = "000001.SZ"
xtdata.download_history_data(
code, period="1d", start_time="20260803", end_time="20260831"
)
bars = xtdata.get_market_data_ex(
field_list=["open", "high", "low", "close", "volume"],
stock_list=[code],
period="1d",
start_time="20260803",
end_time="20260831",
count=-1,
dividend_type="none",
fill_data=True,
)
print(type(bars), bars)
if isinstance(bars, dict):
data = bars.get(code)
print("last five rows:", data.tail(5) if hasattr(data, "tail") else data)
period="1d" 表示日线,count=-1 表示读取区间内的数据,dividend_type="none" 表示不复权。常见返回为“证券代码 → DataFrame”;具体结构取决于大 QMT 返回值,迁移时先打印类型和字段。桥接传输后的索引、类型也需核对,再接入原策略计算。
下载操作发生在 QMT 侧。首次下载可能较慢,空结果时先检查时间区间、下载状态、行情权限和数据源;批量回测读取速度与 MiniQMT 本地数据读取不同。get_local_data(data_dir=...) 的同名参数不代表接管原来的 MiniQMT 本地数据目录。
原来用 from xtquant import xtdata,现在改为下面的导入。保留 subscribe_quote(..., callback=...) 和 unsubscribe_quote(seq);订阅返回的序号用于取消对应订阅。
from cfquant import xtdata
def on_quote(data):
print("quote:", data)
seq = xtdata.subscribe_quote("000001.SZ", period="tick", callback=on_quote)
if seq is None or int(seq) <= 0:
raise RuntimeError("Quote subscription failed")
print("subscribe_id:", seq)
try:
xtdata.run() # 保持进程运行,Ctrl+C 结束
except KeyboardInterrupt:
pass
finally:
xtdata.unsubscribe_quote(seq)
订阅成功不等于立即有行情变化,非交易时段可能没有推送。回调里先打印实际数据结构,再接入策略;耗时计算和网络请求应交给独立队列处理,避免阻塞回调线程。
原来的 XtQuantTraderCallback 子类可保留常用方法名,把导入前缀改成 cfquant。下面是独立的监听脚本,不主动下单;账号有对应事件时才会输出。
from cfquant.xttrader import XtQuantTrader, XtQuantTraderCallback
from cfquant.xttype import StockAccount
class TradeCallback(XtQuantTraderCallback):
def on_stock_order(self, order):
print("order:", order.order_id, order.order_status,
order.traded_volume, order.order_remark)
def on_stock_trade(self, trade):
print("trade:", trade.order_id, trade.stock_code,
trade.traded_volume, trade.traded_price)
def on_order_error(self, error):
print("order_error:", error.error_id, error.error_msg)
def on_cancel_error(self, error):
print("cancel_error:", error.error_id, error.error_msg)
def on_order_stock_async_response(self, response):
print("async_order:", response.seq, response.order_id,
response.order_remark)
def on_disconnected(self):
print("Trading connection closed")
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
trader = XtQuantTrader("", 0)
try:
trader.register_callback(TradeCallback())
trader.start()
if trader.connect() != 0:
raise RuntimeError("Connection failed")
if trader.subscribe(account) != 0:
raise RuntimeError("Account subscription failed")
trader.run_forever() # 保持进程运行,Ctrl+C 结束
except KeyboardInterrupt:
pass
finally:
trader.stop()
行情订阅和账号交易订阅是两件事:前者用 xtdata.subscribe_quote(),后者用 trader.subscribe(account)。监听不到交易事件时,检查账号订阅、QMT 入口状态和网页“回调”页;不要把 run_forever() 放在后续还要执行的下单代码之前。
原版和当前的常用下单签名都是 order_stock(account, stock_code, order_type, order_volume, price_type, price, strategy_name, order_remark)。股票买卖用 xtconstant.STOCK_BUY / STOCK_SELL,限价用 xtconstant.FIX_PRICE;迁移时同时替换常量模块的导入。
下面是完整的当前写法,默认只打印参数。确认账号、代码、数量和价格后,显式开启 ENABLE_ORDER 才会提交真实委托;示例价格仅用于展示参数。
from uuid import uuid4
from cfquant import xtconstant
from cfquant.xttrader import XtQuantTrader
from cfquant.xttype import StockAccount
ENABLE_ORDER = False
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
order_params = dict(
stock_code="000001.SZ",
order_type=xtconstant.STOCK_BUY,
order_volume=100,
price_type=xtconstant.FIX_PRICE,
price=10.00,
strategy_name="python_demo",
order_remark="demo_" + uuid4().hex[:12],
)
if not ENABLE_ORDER:
print("Preview only:", account.account_id, order_params)
else:
trader = XtQuantTrader("", 0)
try:
trader.start()
if trader.connect() != 0 or trader.subscribe(account) != 0:
raise RuntimeError("Connection or subscription failed")
order_id = trader.order_stock(account, **order_params)
print("order_id:", order_id)
print("order:", trader.query_stock_order(account, order_id))
finally:
trader.stop()
与原来相比,这个下单调用本身可沿用,连接改用 XtQuantTrader("", 0)。order_volume 在此股票例子中以股为单位,其他品种按各自交易单位填写。每次请求使用可追踪的备注;备注有助于关联查询和回调,并不提供自动去重保证。
原来的 cancel_order_stock(account, order_id) 调用可保留。下面先查询可撤委托,只有开启撤单且填入对应编号才提交;不会自动撤掉账号下的全部订单。
from cfquant.xttrader import XtQuantTrader
from cfquant.xttype import StockAccount
ENABLE_CANCEL = False
TARGET_ORDER_ID = "" # 填写查询或回调中得到的 order_id
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
trader = XtQuantTrader("", 0)
try:
trader.start()
if trader.connect() != 0 or trader.subscribe(account) != 0:
raise RuntimeError("Connection or subscription failed")
orders = trader.query_stock_orders(account, cancelable_only=True)
for order in orders:
print(order.order_id, order.stock_code, order.order_remark)
if ENABLE_CANCEL:
target = next(
(order for order in orders
if str(order.order_id) == TARGET_ORDER_ID), None
)
if target is None:
raise RuntimeError("Target order is not in the cancelable list")
result = trader.cancel_order_stock(account, target.order_id)
print("cancel_result:", result)
else:
print("Query only; no cancellation submitted")
finally:
trader.stop()
| 接口 / 结果 | 如何确认 |
|---|---|
order_stock() | 返回委托编号;失败可能返回 -1 或抛出异常。拿到编号不代表已成交,应查委托状态或看成交回调;刚提交时单笔查询也可能暂时为空。 |
order_stock_async() | 与同步下单的参数相同,但返回的是请求序号 seq,不是委托编号。通过本交易实例的 on_order_stock_async_response 关联 response.seq 和 response.order_id,不能用 seq 撤单。 |
cancel_order_stock() | 0 表示撤单请求调用成功,-1 表示失败;最终是否撤成,以后续委托状态或撤单错误回调为准。查询之后订单也可能已经成交。 |
| 超时 / 连接异常 | 结果可能尚未确认。先按账号、代码、备注核对委托和回调,再决定是否重试,避免重复提交。 |
使用异步下单时,把调用放在第 6 节注册回调和订阅账号之后、run_forever() 之前,并保留进程等待回报。当前只读 query_*_async() 使用后台查询并立即返回请求序号,查询结果由独立回调线程派发;查询或回调异常会记录到 cfquant.xttrader 日志。这个序号不是委托编号,也不代表原版专用响应线程开关已实现。
每个请求携带明确的账号对象,Web 根据绑定路由到相应 QMT。普通账户用 STOCK,信用账户用 CREDIT;同一个账号数字配上不同账户类型,含义也不同。
from cfquant.xttrader import XtQuantTrader
from cfquant.xttype import StockAccount
accounts = [
StockAccount("YOUR_STOCK_ACCOUNT_ID", "STOCK"),
StockAccount("YOUR_CREDIT_ACCOUNT_ID", "CREDIT"),
]
trader = XtQuantTrader("", 0)
try:
trader.start()
if trader.connect() != 0:
raise RuntimeError("Connection failed")
for account in accounts:
if trader.subscribe(account) != 0:
raise RuntimeError("Subscription failed: " + account.account_id)
asset = trader.query_stock_asset(account)
print(account.account_id, asset)
finally:
trader.stop()
其他账户类型还有 FUTURE、FUTURE_OPTION、STOCK_OPTION。对应业务要使用匹配的买卖 / 开平仓常量和合约代码,不能只换账户类型就照搬股票下单参数。xtdata 的行情调用不接收这个交易账号参数,多账号共享行情源需在 Web“绑定”页配置。
如果原来用的已经是 cfquant,并手工指定 Pipe 或 LTtx 通道,当前常规部署可改用默认 auto。Web 在线且自动发现成功时,请求进入 Web 统一路由,再按账号绑定选择通用、极致或高级模式;发现不到 Web 注册信息时,会回退到通用 PipeHub。
from cfquant import configure configure(transport="ctypes", pipe_name=r"\\.\pipe\cfquant_pipe_hub") # 然后再创建 trader 或调用 xtdata
from cfquant import configure, xtdata # 无旧配置时可省略;迁移旧脚本时可显式选择 auto configure(transport="auto") print(xtdata.get_full_tick(["000001.SZ"]))
检查旧脚本及启动环境中的 CFQUANT_TRANSPORT,避免仍被固定为 ctypes 或 lttx。自定义 LTtx 地址、端口、认证或 Pipe 名称仍需保持一致;auto 不会自动修复这些连接参数。
from cfquant import configure
configure(
transport="web_lttx",
host="127.0.0.1",
port=2049,
token="LTtx",
web_request_channel="cfquant.web.request",
timeout=15,
)
# 然后再创建 trader 或调用 xtdata
这里的 2049 是默认 LTtx 端口,LTtx 是默认通信 token;都要与实际服务配置一致。它们不同于网页 HTTP 地址(默认 8765)及 HTTP API Key。configure() 应在创建交易实例、查询和订阅之前调用;切换通道后重启外部策略进程更便于确认配置生效。
| 现象 | 检查与处理 |
|---|---|
ModuleNotFoundError: cfquant | 在策略使用的解释器执行 python -m pip install cfquant。核对 IDE 解释器和 cfquant.__file__,确认没有装到另一个环境或导入旧副本。 |
连接返回 -1,或出现 CfquantTimeout | 检查 Web、LTtx / PipeHub 和对应 QMT 入口是否在线,再核对旧环境变量和自定义端口。交易请求超时后先查委托,不要立即重复下单。 |
| 连接成功,但账号查询为空或报错 | 核对资金账号字符串、STOCK / CREDIT 等账户类型、绑定目录和 QMT 登录账号;先在网页执行同一查询,区分绑定问题与脚本问题。 |
| 快照 / 历史数据为空 | 核对证券代码后缀、日期范围、周期、行情源、下载完成情况及券商权限。先查单只证券的小区间,再扩大批量。 |
| 没有回调 | 检查回调注册、对应的行情或账号订阅、进程是否仍在运行,以及 QMT 是否实际产生了该事件;stop() 后本进程不再监听。 |
| 同名接口不可用 / 字段不一致 | 对照项目文档 docs/xtquant原版接口适配清单.md、docs/xtdata平替追踪.md 和 docs/xttrader平替追踪.md。条件适配接口仍依赖券商大 QMT 暴露对应 callable;客户端连接管理、本地目录语义及部分异步接口不能只替换导入就假定完全兼容。 |
建议迁移顺序:确认导入版本 → 只读行情 → 资金和持仓 → 回调监听 → 单笔委托和撤单 → 多账号与策略自动运行。每一步都核对实际返回值,再接入下一段策略逻辑。
cfquant.cftrader 是项目扩展的交易模块。通过 CfQuantTrader(trader) 复用已有 XtQuantTrader 实例;连接、订阅、查询、单笔撤单和回调继续交给原实例。
整批请求通过一次 RPC 发送到大 QMT,再由 QMT 内部连续调用 passorder 或 cancel。 100 笔操作对应一次批量通信、100 次本地调用,减少逐笔通信往返。批量接口适合组合调仓、批量止盈止损,以及撤掉一组未成委托;最终成交或撤成仍以委托查询和回调为准。
需要更新并重启 Web 服务和 QMT 核心;极致模式需重新导入更新后的内置脚本。启用同账号 SH/SZ 独立市场路由时,批量撤单建议每笔带 stock_code 或 market,便于路由到正确交易端。
本机假 QMT 桥基准(2026-09-11,100 单,5 次预热、30 次采样)显示:批量同步中位 4.802 ms,100 次单笔同步中位 9.169 ms;批量异步中位 7.093 ms,100 次单笔异步中位 9.053 ms。该结果只衡量 SDK 到桥接分发开销,不代表真实券商柜台耗时。
| 100 单路径 | RPC 次数 | 中位耗时 | 平均耗时 |
|---|---|---|---|
批量同步 order_stock_batch | 1 | 4.802 ms | 4.901 ms |
单笔同步循环 order_stock | 100 | 9.169 ms | 8.930 ms |
批量异步 order_stock_batch_async | 1 | 7.093 ms | 7.122 ms |
单笔异步循环 order_stock_async | 100 | 9.053 ms | 9.186 ms |
模拟账号实测(2026-09-11 02:45):信用账号 900010001595 通过 Web LTtx 统一路由连接 acct_4b2b38c167,对 600000.SH 以 8.88 元、每笔 100 股测试;四条路径各 100 单均返回 submitted=100,复核 400 单均已撤。
| 模拟账号 100 单路径 | RPC 次数 | 提交耗时 | 单笔均摊 |
|---|---|---|---|
批量同步 order_stock_batch | 1 | 963.252 ms | 9.6325 ms |
单笔同步循环 order_stock | 100 | 23250.174 ms | 232.5017 ms |
批量异步 order_stock_batch_async | 1 | 52.578 ms | 0.5258 ms |
单笔异步循环 order_stock_async | 100 | 2952.912 ms | 29.5291 ms |
| 接口 | 参数与结果 |
|---|---|
order_stock | 保留原单笔参数顺序,返回订单号。 |
order_stock_async | 保留原单笔参数顺序,返回请求序号 seq。 |
order_stock_batch | account, orders, strategy_name="", order_remark="", stop_on_error=False;返回逐笔订单号和批量统计。 |
order_stock_batch_async | 与同步批量参数相同;返回逐笔请求序号和批量统计。 |
cancel_order_stock_batch | account, order_ids, stop_on_error=False;返回逐笔撤单调用结果。 |
cancel_order_stock_batch_async | 与同步批量撤单参数相同;返回逐笔请求序号和撤单调用结果。 |
orders 是订单字典列表。每笔必填 stock_code、order_type、order_volume、price_type、price,沿用原常量和字段名。可单独指定 strategy_name 和 order_remark;未填备注时自动生成逐笔备注。
每批对应一个账号,参数会在发送前整批校验。下单数量必须为正整数,价格必须为有限数值,字段拼写错误会直接报错;撤单列表每项至少包含 order_id。完整文档位于 docs/cftrader批量交易.md。
QMT 先连续提交整批,再集中解析订单号,共用一个等待窗口,不在每笔下单之间等待编号。示例默认预览参数;替换账号、代码和价格,开启 ENABLE_TRADING 后提交。
from cfquant import cftrader, xtconstant
from cfquant.xttrader import XtQuantTrader
from cfquant.xttype import StockAccount
ENABLE_TRADING = False
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
orders = [
dict(stock_code="600000.SH", order_type=xtconstant.STOCK_BUY,
order_volume=100, price_type=xtconstant.FIX_PRICE, price=10.0),
dict(stock_code="000001.SZ", order_type=xtconstant.STOCK_BUY,
order_volume=100, price_type=xtconstant.FIX_PRICE, price=9.0),
]
trader = XtQuantTrader("", 0)
orders_api = cftrader.CfQuantTrader(trader)
if ENABLE_TRADING:
try:
trader.start()
if trader.connect() != 0 or trader.subscribe(account) != 0:
raise RuntimeError("Connection or subscription failed")
result = orders_api.order_stock_batch(
account, orders, strategy_name="rebalance", stop_on_error=True,
)
for row in result["results"]:
print(row["index"], row["status"], row["order_id"], row["error"])
finally:
trader.stop()
else:
print("Preview only:", orders)
results 与输入等长且顺序一致,index 从 0 开始。获得订单号不代表成交;QMT 明确拒绝记录为 failed,已提交但编号未确认记录为 unknown,此时后续订单可能已经提交。
SDK 预分配逐笔请求序号,将整批一次发送到 QMT;QMT 连续下单后返回逐笔受理状态,不等待订单号或成交。回报仍进入原来的 XtQuantTraderCallback,通过 response.seq 关联请求序号与订单号。
from cfquant import cftrader, xtconstant
from cfquant.xttrader import XtQuantTrader, XtQuantTraderCallback
from cfquant.xttype import StockAccount
class Callback(XtQuantTraderCallback):
def on_order_stock_async_response(self, response):
print("response:", response.seq, response.order_id, response.order_remark)
def on_stock_order(self, order):
print("order:", order.order_id, order.order_status)
def on_stock_trade(self, trade):
print("trade:", trade.order_id, trade.traded_volume)
def on_order_error(self, error):
print("error:", error)
ENABLE_TRADING = False
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
orders = [
dict(stock_code="600000.SH", order_type=xtconstant.STOCK_BUY,
order_volume=100, price_type=xtconstant.FIX_PRICE, price=10.0),
dict(stock_code="000001.SZ", order_type=xtconstant.STOCK_BUY,
order_volume=100, price_type=xtconstant.FIX_PRICE, price=9.0),
]
trader = XtQuantTrader("", 0)
orders_api = cftrader.CfQuantTrader(trader)
if ENABLE_TRADING:
try:
trader.register_callback(Callback())
trader.start()
if trader.connect() != 0 or trader.subscribe(account) != 0:
raise RuntimeError("Connection or subscription failed")
result = orders_api.order_stock_batch_async(
account, orders, strategy_name="rebalance", stop_on_error=False,
)
for row in result["results"]:
print(row["index"], row["status"], row["seq"], row["order_remark"])
trader.run_forever()
except KeyboardInterrupt:
pass
finally:
trader.stop()
else:
print("Preview only:", orders)
回调可能早于批量方法返回;可先用每笔 order_remark 关联。批量返回后需保持进程运行,seq 不能用于撤单。原单笔异步与批量异步共用请求序号和回调去重。
信用账户使用 StockAccount("YOUR_CREDIT_ACCOUNT_ID", "CREDIT")。融资买入使用 xtconstant.CREDIT_FIN_BUY,担保品卖出使用 xtconstant.CREDIT_SELL,同步和异步批量字段一致。期货、期货期权和股票期权沿用原账号类型、合约代码和对应常量。
批量撤单只提交撤单请求,不判断原委托最终是否已经撤成。返回 submitted 表示 QMT 的 cancel 调用已被接受;最终状态仍要查询委托表或等待撤单错误回调。
from cfquant import cftrader
from cfquant.xttrader import XtQuantTrader
from cfquant.xttype import StockAccount
ENABLE_TRADING = False
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
order_ids = [
dict(order_id="1001", stock_code="600000.SH"),
dict(order_id="1002", market="SZ"),
]
trader = XtQuantTrader("", 0)
orders_api = cftrader.CfQuantTrader(trader)
if ENABLE_TRADING:
try:
trader.start()
if trader.connect() != 0 or trader.subscribe(account) != 0:
raise RuntimeError("Connection or subscription failed")
result = orders_api.cancel_order_stock_batch(
account, order_ids, stop_on_error=False,
)
for row in result["results"]:
print(row["index"], row["status"], row["order_id"], row["cancel_result"], row["error"])
finally:
trader.stop()
else:
print("Preview only:", order_ids)
异步批量撤单使用 cancel_order_stock_batch_async。SDK 为每笔撤单分配 seq,QMT 本地连续调用 cancel 后返回逐笔受理状态,并继续触发原 on_cancel_order_stock_async_response 和 on_cancel_error 回调。
from cfquant import cftrader
from cfquant.xttrader import XtQuantTrader, XtQuantTraderCallback
from cfquant.xttype import StockAccount
class Callback(XtQuantTraderCallback):
def on_cancel_order_stock_async_response(self, response):
print("cancel response:", response.seq, response.order_id, response.cancel_result)
def on_cancel_error(self, error):
print("cancel error:", error)
ENABLE_TRADING = False
account = StockAccount("YOUR_ACCOUNT_ID", "STOCK")
order_ids = [
dict(order_id="1001", stock_code="600000.SH"),
dict(order_id="1002", market="SZ"),
]
trader = XtQuantTrader("", 0)
orders_api = cftrader.CfQuantTrader(trader)
if ENABLE_TRADING:
try:
trader.register_callback(Callback())
trader.start()
if trader.connect() != 0 or trader.subscribe(account) != 0:
raise RuntimeError("Connection or subscription failed")
result = orders_api.cancel_order_stock_batch_async(
account, order_ids, stop_on_error=False,
)
for row in result["results"]:
print(row["index"], row["status"], row["seq"], row["cancel_result"], row["error"])
trader.run_forever()
except KeyboardInterrupt:
pass
finally:
trader.stop()
else:
print("Preview only:", order_ids)
顶层返回 batch_id、account、asynchronous、ok、total、attempted、submitted、failed、unknown、skipped 和 results。每行包含状态、原始索引、代码、策略名、备注、订单号或请求序号及错误信息。
| 状态 | 含义 | 后续订单 |
|---|---|---|
submitted | 已获得订单号或请求序号,行内 ok=True;最终成交以原回报为准。 | 继续提交。 |
failed | QMT 下单明确拒绝,同步、异步均适用,行内 ok=False。 | stop_on_error=True 时停止。 |
unknown | 同步编号未确认、整批回包异常或本地下单异常,行内 ok=None。 | 先查委托与回调;整批通信超时不代表 QMT 停止执行。 |
skipped | 本次未调用该行下单,行内 ok=None。 | 不会自动补发。 |
execution="qmt" 标明采用 QMT 内批量执行协议,qmt_submit_ms 是 QMT 提交循环耗时,不包含通信与编号解析。异步所有行保留预分配 seq,只有 submitted 代表确认受理。
批量撤单结果额外有 operation="cancel";每行包含 order_id、可选 stock_code/market、seq、cancel_result 和 error。cancel_result=0 表示 QMT 撤单调用成功,不代表原委托已经处于已撤状态。
QMT 本地下单异常会停止尚未执行的行。trader.set_timeout() 对整批 RPC 生效,整批超时或回包丢失时所有未确认行标为 unknown,保留原异步关联;SDK 和 Web 均不自动重试或切换通道重发。已提交订单不回滚,不保证原子提交或同时成交。
启用 SH/SZ 独立市场路由时,连续同目标市场的订单组成一段,每段一个批量请求,由对应 QMT 本地执行;返回结果仍保持输入顺序。
本教程介绍如何在大 QMT 中部署 cfquant,并通过本地 Web 控制台或外部 Python 调用行情、查询、交易和回调能力。
start_cfquant.bat 或 cfquant --open-browser 打开 Web 控制台,默认地址是 http://127.0.0.1:8765/。
bin.x64 和运行模式,能查到资产、持仓或行情即部署成功。
| 模式 | 入口脚本 | QMT 数量 | 说明 |
|---|---|---|---|
| 通用模式 | CFQUANT_CTYPE_ALL_LOWLAT.py |
1 个 | 默认推荐,适合大多数行情、查询、下单、撤单和回调场景。 |
| 极致模式 | CFQUANT_LITE.py |
1 个 | 适合 QMT 对 Python 包导入有限制,或国泰君安、国泰海通等环境。 |
| 高级模式 | CFQUANT.py + CFQUANT_TRADE_LOWLAT.py |
2 个 | 部署复杂,但外部程序到 QMT 内部的下单链路延迟更低。 |
两个 QMT 都需要完成登录。自动启动选项仅启动绑定的主 QMT 目录,极速交易端需单独启动并登录。
13696119612,服务费 100 元/次。适合同一资金账号拆成上海、深圳两个 QMT 交易端的场景。网页保存一个主账号,交易请求按证券后缀自动分流。
*.SH
走上海 QMT 子桥。
*.SZ
走深圳 QMT 子桥。
批量下单
自动拆成 SH、SZ 两组请求,再合并返回。
bin.x64 目录。_SH.py,深圳 QMT 加载 _SZ.py。SZ399001 或 000001.SZ;不要把 _SZ.py 挂在 SH000300 上。| 模式 | 上海 | 深圳 |
|---|---|---|
| 通用 | CFQUANT_CTYPE_ALL_LOWLAT_SH.py |
CFQUANT_CTYPE_ALL_LOWLAT_SZ.py |
| 极致 | CFQUANT_LITE_SH.py |
CFQUANT_LITE_SZ.py |
| 高级交易端 | CFQUANT_TRADE_LOWLAT_SH.py |
CFQUANT_TRADE_LOWLAT_SZ.py |
bridge_id 是内部路由标识。普通用户只需要填写账号和 QMT 目录,Web 会按目录复用或分配通道。
| 场景 | 建议 |
|---|---|
| 单 QMT 单账号 | 使用默认通道 default。 |
| 单 QMT 多账号 | 多个账号可共用同一个 QMT 目录。 |
| 多 QMT 多账号 | 每个账号填写实际登录它的 QMT bin.x64 目录。 |
bridge_id 要和网页绑定状态一致。account_id,不要手动写死 bridge_id。账号绑定是运行配置的核心。每个账号保存自己的账户类型、QMT 目录、首选模式、启用状态和共享行情源标记。
| 字段 | 说明 |
|---|---|
| 资金账号 | 外部请求按它路由,普通和信用账户要区分账户类型。 |
| QMT 目录 | 填写对应 QMT 的安装目录或 bin.x64,用于自动同步项目代码、内部策略代码、身份文件和后续更新。 |
| 运行模式 | ctypes、lite、lttx 三选一。 |
| 高级模式第二目录 | 选择高级模式时必须填写另一个 QMT 的安装目录或 bin.x64。 |
| 共享行情源 | 多账号时只选一个稳定账号,避免重复订阅全推行情。 |
交易页适合做实盘前验证:先查资金和持仓,再用小数量测试下单、撤单和回调。
| 能力 | 说明 |
|---|---|
| 单笔下单 | POST /api/order,对应 order_stock。 |
| 批量下单 | POST /api/orders/batch,逐笔提交并合并结果。 |
| 撤单 | POST /api/cancel,委托状态仍以 QMT 和回调为准。 |
| 信用查询 | 支持信用资产、标的、担保品、合约等只读探测。 |
| 回调 | GET /api/callbacks 或 WS /ws/callbacks 接收委托、成交和错误事件。 |
延迟只表示本机程序到 QMT 脚本的链路量级,不代表券商柜台或交易所确认速度。实际结果会受 QMT 版本、券商环境、机器负载和交易时段影响。
| 链路 | 查询量级 | 下单请求样本 | 适合场景 |
|---|---|---|---|
| 通用 ctypes | 约 180-260 ms | 约 20 ms | 部署简单、功能验证、多账号日常使用。 |
| 高级普通端 | 约 250 ms | 约 176 ms | 查询、行情、账号级回调。 |
| 高级极速交易端 | 约 1-4 ms | 约 1 ms | 追求更低本机到 QMT 下单链路。 |
“接口”页就是在线调试台。外部程序接入前,先在这里跑通同一个账号和参数。
curl -H "X-API-Key: your-api-key" ^ "http://127.0.0.1:8765/api/account?account_id=YOUR_ACCOUNT_ID§ions=asset,positions&force=1"
启用 API Key 后,HTTP 使用 X-API-Key 请求头;WebSocket 使用 apikey 查询参数。
| 接口 | 用途 |
|---|---|
GET /api/account | 资金、持仓、委托、成交。 |
POST /api/order | 单笔下单。 |
POST /api/orders/batch | 批量下单。 |
POST /api/cancel | 撤单。 |
POST /api/data/full-tick | 实时 Tick。 |
POST /api/data/market | K 线和行情数据。 |
POST /api/data/history/download | 历史行情下载。 |
POST /api/data/financial | 财务数据读取。 |
GET /api/callbacks | 读取交易回调。 |
WS /ws/callbacks | 实时交易回调。 |
WS /ws/quotes | 实时行情事件。 |
按层排查最快:先确认 Web 能访问,再看本地服务,再看 QMT 脚本,最后看账号和权限。
| 现象 | 优先检查 | 处理 |
|---|---|---|
| 网页打不开 | Web 端口、启动日志 | 重启服务,查看 log/cfquant_web_server.stderr.log。 |
| QMT 离线 | 入口脚本、QMT 登录、bridge_id |
重启 QMT 入口,确认加载了当前模式对应脚本。 |
| 高级模式缺交易端 | 第二个 QMT 目录、CFQUANT_TRADE_LOWLAT.py |
在绑定页补充极速交易端目录并重新保存。 |
| 查不到账号数据 | 账号类型、QMT 登录账号、绑定目录 | 在绑定页点击验证,确认资金和持仓来自预期账号。 |
| 下单失败 | 确认文本、价格数量、权限、回调错误 | 先小数量测试,再查看回调页和 QMT 日志。 |
| 版本或缓存异常 | 前端版本、浏览器缓存 | 重启 Web 后用 Ctrl + F5 强制刷新。 |