Files
qmt_bridge/reference/xtquant_big_convert/docs/FORMULA_SERVER_FASTPATH.md
T

6.3 KiB
Raw Blame History

FormulaServer 直连快速路径(58600

这是什么

大 QMT 的 58600 端口是 FormulaServer —— QMT 内置的 C++ 行情/参考数据服务。端口取自 QMT 安装目录的 config/formulaserver/formulaserver.ini

[server_formula]
address = 0.0.0.0:58600

QMT 自带 Python 里就有它的官方客户端:bin.x64/Lib/site-packages/qmt_api。协议是 BSON over TCP,帧格式在 qmt_api/net/RPCBase.py

| packLen(uint32 BE) | seq(uint32 BE) | cmd(uint16 BE) | tag(uint16 BE) | BSON body |

cmd = 3NET_CMD_RPC),body 是 {"func": <名字>, "params": {...}} 响应 {"status": 0, "params": {...}}status != 0params 里带 ErrorID/ErrorMsgtag & 7 标记 zlib 压缩,tag >> 8 的低 4 位是 seq 的高位。

为什么值得接

原来所有只读请求都要绕一整圈:

客户端 → redis/zmq → QMT python 策略线程 → ContextInfo → 原路返回

这不只是慢,还要和策略自己抢 QMT 主线程的 GIL —— zmq 传输约 30% 的请求会撞上 ~500ms 的 调度尖峰,就是这么来的。

直连 FormulaServer 完全绕开策略进程:

路径 p50
redis RPC ~13ms
zmq RPC ~0.7ms30% 撞 500ms GIL 尖峰)
FormulaServer 直连 0.07ms,无 GIL 竞争

穿过完整客户端栈(BigQmtRpcClient.call)实测 0.145ms/次,比 redis 快约 90 倍。

能力边界

FormulaServer 只有行情/参考数据。 实测所有账户/交易类方法一律返回 ErrorID 200005 未找到该服务

getAsset / getPositions / getAccountDetail / passorder   -> 200005
getFullTick / getQuote                                   -> 200005

所以它是只读快速路径,不是 RPC 桥的替代品。交易、账户查询、持仓、委托、成交、 五档盘口全部仍然走 RPC。

已接入的方法(10 个)

我们的方法 FormulaServer func
get_instrument / get_instrument_detail / get_instrumentdetail getInstrumentDetail
get_last_volume getLastVolume
get_total_share getTotalShare
get_contract_multiplier getContractMultiplier
get_main_contract getMainContract
get_weight_in_index getWeightInIndex
get_stock_list_in_sector getStockListInSector
get_market_data_ex getMarketData

刻意不接的方法,以及原因

宁可慢,不能悄悄给错数据。以下几项参数语义与我们的调用方不一致:

  • get_trading_dates —— FormulaServer 要的是股票代码。实测:

    {'stockCode': 'SH',        ...} -> {'result': []}          # 静默空
    {'stockCode': '000001.SZ', ...} -> ['20260630', '20260701', ...]
    

    market_bigqmt.get_trading_dates(market, ...) 的调用方传的是市场代码。传错了不报错、 只给空列表,交易日历错了后果太重。

  • get_divid_factors —— 我们是 (stock_code, start_time, end_time) 区间, FormulaServer 是 (stockCode, date) 单日。

  • get_risk_free_rate —— 我们传 index=-1FormulaServer 要 timetag。语义不同。

  • 复权 K 线 —— 实测 dividendTypenonefront 返回完全相同的价格, 说明复权没有生效。因此只有 dividend_type="none"(或空)才走直连,其他复权类型直接 判为 unroutable 回退 RPC。否则策略要前复权、拿到的却是不复权价格,且毫无提示。

字段名坑

FormulaServer 的 getInstrumentDetail 返回 FloatVolumn / TotalVolumn(官方拼写错误), 而原生 xtdata SDK 用的是 FloatVolume / TotalVolume。下游代码按 SDK 拼写读,直接透传会 静默读到 None。所以 _instrument_result 做了别名归一化,两种拼写都保留。

尚未验证

qmt_api/api.pygetMarketData 支持 fields=['quoter'],注释说会返回 askPrice/askVol/bidPrice/bidVollevel1 五档 / level2 十档)。如果这在盘中可用, get_full_tick 也能走直连 —— 这是热路径,收益很大。

但收盘时段实测返回空,无法确认。需要盘中再测一次:

c.request('getMarketData', {'fields': ['quoter'], 'stockCodes': ['000001.SZ'],
                            'startTime': '', 'endTime': '', 'period': 'tick',
                            'dividendType': 'none', 'count': -1})

在确认之前不要接 —— 没验证就上映射,正是订单方向判定踩过的坑。

失败行为

任何失败都自动回退 RPC,所以连不上 58600 的客户端行为与改动前完全一致:

情况 行为
方法不在映射表 supports() 返回 False,直接走 RPC
参数 translate 不了 Unroutable,走 RPC触发熔断(这是单次调用的问题)
服务连不上 / IO 失败 Unroutable + 熔断 failure_cooldown_seconds(默认 30s),期间全部走 RPC
服务端回 200005 该方法永久标记 unimplemented,只停这一个方法,不影响其他
socket 断了(QMT 重启) 自动重连重试一次

依赖

不需要装任何东西。 BSON 编解码内置了无依赖实现;如果环境里有 pymongo 的 bson 或 QMT 的 xtquant.xtbson,会优先用(更快、更久经考验)。两条路径的输出实测逐字节一致, 测试里有对拍用例。

配置

客户端侧,默认开启,通常不用写:

BIGQMT_FORMULA_SERVER_CONFIG = {
    "enabled": True,            # 或环境变量 BIGQMT_FORMULA_ENABLED=0 关闭
    # "host": "127.0.0.1",     # 绑的是 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,
}

也可以写在 BIGQMT_REDIS_CONFIG["formula_server"] 里,后者优先级更高。

排查

client._formula_router().stats()
# {'enabled': True, 'hits': 202, 'misses': 0, 'available': True,
#  'unimplemented': [], 'methods': [...]}

启动时会打一行:

[bigqmt_formula] active at 127.0.0.1:58600 (10 methods routed direct)

熔断时:

[bigqmt_formula] unavailable, falling back to RPC for 30s: connect 127.0.0.1:58600 failed: ...