xtquant-big-convert 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xtquant_big_convert-0.3.0/src/xtquant_big_convert.egg-info → xtquant_big_convert-0.3.2}/PKG-INFO +75 -5
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/README.md +74 -4
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/pyproject.toml +1 -1
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/order_bigqmt.py +79 -1
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/models.py +5 -0
- xtquant_big_convert-0.3.2/src/bigqmt_signal_trader/order_id.py +104 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/qmt_launcher.py +27 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/redis_rpc.py +121 -1
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/version.py +1 -1
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/xtquant_compat.py +198 -15
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2/src/xtquant_big_convert.egg-info}/PKG-INFO +75 -5
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant_big_convert.egg-info/SOURCES.txt +1 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/LICENSE +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/setup.cfg +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/BIGQMT_REDIS_DRYRUN.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/BIGQMT_ZMQ_BACKTEST.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/BIGQMT_ZMQ_DRYRUN.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/__init__.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/__main__.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/broker.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/client.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/data_feed.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/engine.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/models.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/protocol.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/qmt_runtime.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/server.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/strategy.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_backtest/zmq_server.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/README.md +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/__init__.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapter_factory.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/__init__.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/market_bigqmt.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/order_dryrun.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/position_bigqmt.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/position_sync_redis.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/redis_common.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/signal_redis.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/adapters/state_redis.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/app.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/code_utils.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/contracts.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/download_jobs.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/exec_events.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/formula_server.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/full_tick_cache.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/init_config.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/local_cache.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/logging_setup.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/price_engine.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/quote_push_channel.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/quote_subscription_manager.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/risk_guard.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/runner.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/runtime_bigqmt.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/sync.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/transports/__init__.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/transports/base.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/transports/factory.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/transports/mysql_transport.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/transports/redis_transport.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/transports/shm_transport.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/transports/zmq_transport.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/whole_quote_session.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader_diagnostic.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader_dryrun.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader_redis_dryrun.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader_redis_rpc_runtime.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader_strategy.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant/__init__.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant/xtconstant.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant/xtdata.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant/xttrader.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant/xttype.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant_big_convert.egg-info/dependency_links.txt +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant_big_convert.egg-info/entry_points.txt +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant_big_convert.egg-info/requires.txt +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/xtquant_big_convert.egg-info/top_level.txt +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/tests/test_single_file_build.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/tests/test_version_stamp.py +0 -0
- {xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/tests/test_xtquant_shim_import.py +0 -0
{xtquant_big_convert-0.3.0/src/xtquant_big_convert.egg-info → xtquant_big_convert-0.3.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xtquant-big-convert
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Big QMT RPC bridge and MiniQMT-compatible adapter layer (redis/zmq/mysql transports)
|
|
5
5
|
Author: litaolemo
|
|
6
6
|
License: MIT
|
|
@@ -461,10 +461,15 @@ python -m bigqmt_signal_trader.qmt_launcher restart --dir "D:\国金证券QMT交
|
|
|
461
461
|
| `exe` | 直接起 `XtItClient.exe`,靠终端自身恢复会话 | 否 |
|
|
462
462
|
| `login` | 起 exe 后向登录框输入账号密码 | 是,需 pywin32 |
|
|
463
463
|
|
|
464
|
-
|
|
465
|
-
`
|
|
466
|
-
|
|
467
|
-
|
|
464
|
+
**`login` 模式需要未锁屏的交互式桌面。** 它用的是 `keybd_event` / `mouse_event`
|
|
465
|
+
物理输入(经 ctypes),不是 `SendMessage`——消息式输入投不到 Qt 对话框的焦点控件上,
|
|
466
|
+
当别的窗口在前台时会静默失败,什么也不输入。物理输入要求对话框在最前,所以启动前会
|
|
467
|
+
先把它置顶并核验;锁屏或 RDP 注销的会话直接抛 `QmtLauncherError` 而不是打一半密码。
|
|
468
|
+
|
|
469
|
+
> 需要**无人值守定时重启**的话,用 `linkmini` / `bat` / `exe` 三种模式,它们不碰登录框,
|
|
470
|
+
> 锁屏也能跑。只有 `login` 受这条限制。
|
|
471
|
+
|
|
472
|
+
密码从环境变量 `BIGQMT_LOGIN_USER` / `BIGQMT_LOGIN_PASSWORD` 读,不走命令行参数——argv
|
|
468
473
|
对同机任何进程可见。
|
|
469
474
|
|
|
470
475
|
两个设计要点:
|
|
@@ -660,6 +665,67 @@ credit_positions = credit_trader.query_stock_positions(credit_acc)
|
|
|
660
665
|
|
|
661
666
|
---
|
|
662
667
|
|
|
668
|
+
## 与 MiniQMT 的兼容性对照
|
|
669
|
+
|
|
670
|
+
本项目的目标是让照着 MiniQMT (`xtquant`) 写的代码不改就能跑。下表列出**返回值契约**——类型不对不会报错,只会让判断悄悄反过来,所以单独列出来。
|
|
671
|
+
|
|
672
|
+
### 返回值:与 MiniQMT 一致
|
|
673
|
+
|
|
674
|
+
| 接口 | 返回 | 说明 |
|
|
675
|
+
|---|---|---|
|
|
676
|
+
| `order_stock()` | `int` | 成功为正数,失败 `-1` |
|
|
677
|
+
| `order_stock_async()` | `int` | 请求序号 seq,结果走 `on_order_stock_async_response` |
|
|
678
|
+
| `cancel_order_stock()` | `int` | **`0` 成功,`-1` 失败**(不是 True/False) |
|
|
679
|
+
| `cancel_order_stock_sysid()` | `int` | 同上 |
|
|
680
|
+
| `cancel_order_stock_async()` | `int` | seq |
|
|
681
|
+
| `connect()` / `start()` | `int` | `0` 成功 |
|
|
682
|
+
| `subscribe()` / `unsubscribe()` | `int` | `0` 成功 |
|
|
683
|
+
| `query_stock_asset()` | 对象 | `.cash` / `.total_asset` 等属性 |
|
|
684
|
+
| `query_stock_positions()` | `list[对象]` | |
|
|
685
|
+
| `query_stock_orders()` / `query_stock_trades()` | `list[对象]` | |
|
|
686
|
+
| `subscribe_quote()` / `subscribe_whole_quote()` | `int` | 订阅号,传给 `unsubscribe_quote()` |
|
|
687
|
+
| `get_full_tick()` | `dict` | `{code: {...}}` |
|
|
688
|
+
| `get_market_data_ex()` | `dict[str, DataFrame]` | |
|
|
689
|
+
|
|
690
|
+
### 订单号:既是 int 也是 str
|
|
691
|
+
|
|
692
|
+
MiniQMT 的 `order_id` 是 int(委托编号),`order_sysid` 是 str(柜台合同编号)。大 QMT **没有前者**——`get_trade_detail_data` 只给 `m_strOrderSysID` 这个字符串。
|
|
693
|
+
|
|
694
|
+
所以这里的 `order_id` 是一个 int 子类,两种形态同时成立:
|
|
695
|
+
|
|
696
|
+
```python
|
|
697
|
+
order_id = xt_trader.order_stock(acc, "600000.SH", 23, 100, 11, 10.0, "s", "")
|
|
698
|
+
|
|
699
|
+
isinstance(order_id, int) # True —— MiniQMT 写法照常
|
|
700
|
+
order_id > 0 # True
|
|
701
|
+
order_id == -1 # 失败时才 True
|
|
702
|
+
|
|
703
|
+
str(order_id) # '合同编号' —— 券商给的原始字符串
|
|
704
|
+
xt_trader.cancel_order_stock(acc, order_id) # 撤单送回的是原始字符串
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
合同编号是纯数字时(多数券商),int 值就是那个数字,两种形态完全一致;不是纯数字时 int 是一个稳定的正数替身,而撤单、打印用的仍是真实编号。
|
|
708
|
+
|
|
709
|
+
把 order_id 存进数据库再取出来(变成普通 int)也能撤单——客户端记着最近 4096 个的对应关系。想要字符串就用 `.order_sysid`,它一直是 str。
|
|
710
|
+
|
|
711
|
+
同样的规则适用于 `XtOrder.order_id`、`XtTrade.order_id`,以及回调对象 `XtOrderError` / `XtCancelError` / `XtOrderResponse` 里的 `order_id`。
|
|
712
|
+
|
|
713
|
+
### 行为差异(不是返回值,但会咬人)
|
|
714
|
+
|
|
715
|
+
| 项目 | MiniQMT | 本项目 |
|
|
716
|
+
|---|---|---|
|
|
717
|
+
| `get_full_tick(["SH"])` | 全市场 | **默认只取股票**(1.08s);要全部传 `types=["all"]`(7.4s,含地方债等 26744 只) |
|
|
718
|
+
| `get_instrument_detail()` 查不到 | `None` | `{}`(两者都是 falsy,`if not detail` 通用) |
|
|
719
|
+
| `download_history_data()` | 无返回 | 返回 `{"finished": n, "total": n}`(多给的信息,可忽略) |
|
|
720
|
+
| 账户类型 | `StockAccount(id, "CREDIT")` 即可 | 还需服务端 `BIGQMT_ACCOUNT_TYPE = "CREDIT"`,**客户端的类型不会传到服务端** |
|
|
721
|
+
| 委托类型常量 | `xtconstant.order_type` | 内部会翻译成 `passorder` 的 opType(两套编号,专项两融 40–45 → 70–75) |
|
|
722
|
+
|
|
723
|
+
### 本项目的扩展(MiniQMT 没有)
|
|
724
|
+
|
|
725
|
+
这些不是兼容项,是多出来的:`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()`。
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
663
729
|
## 环境要求与依赖安装
|
|
664
730
|
|
|
665
731
|
本系统分两部分,各自需要自己的 Python 环境和依赖:
|
|
@@ -743,6 +809,8 @@ cd D:\国金证券QMT交易端
|
|
|
743
809
|
|
|
744
810
|
## 快速开始
|
|
745
811
|
|
|
812
|
+
> 第一次部署、只想要最短路径?直接看 [docs/DEPLOY_QUICKSTART.md](docs/DEPLOY_QUICKSTART.md)(单账号五步跑通 + 常见问题表)。
|
|
813
|
+
|
|
746
814
|
> 前置:客户端已按上面「A. 客户端」装好包;服务端按「B. 服务端」装好所选传输的依赖。下面是从零跑通整套流程的步骤。
|
|
747
815
|
>
|
|
748
816
|
> 只想把配置生成出来的话,跑 [`bigqmt-init`](#配置向导bigqmt-init) 即可——第 3 步的两份配置它会替你写好,选单文件部署还会顺带把构建也做了。
|
|
@@ -1231,6 +1299,8 @@ python qmt-trader/scripts/qmt.py buy 600000.SH 100 --price 7.50 --dry-run
|
|
|
1231
1299
|
## 相关文档
|
|
1232
1300
|
|
|
1233
1301
|
- [CHANGELOG.md](CHANGELOG.md) — **版本变更记录**(新增/修复/变更)
|
|
1302
|
+
- [docs/DEPLOY_QUICKSTART.md](docs/DEPLOY_QUICKSTART.md) — **单账号部署快速开始**(最短路径 + 部署期常见问题表)
|
|
1303
|
+
- [docs/LATENCY_REPORT.md](docs/LATENCY_REPORT.md) — **延迟测试报告**(传输层对比、FormulaServer 直连、下单链路、方法论)
|
|
1234
1304
|
- [docs/RPC_API_REFERENCE.md](docs/RPC_API_REFERENCE.md) — **全部 RPC 方法参考**(参数、返回值、别名、大 QMT 能力边界)
|
|
1235
1305
|
- [docs/FORMULA_SERVER_FASTPATH.md](docs/FORMULA_SERVER_FASTPATH.md) — FormulaServer(58600) 直连快速路径:协议、映射表、能力边界与回退行为
|
|
1236
1306
|
- [docs/SUBSCRIBE_WHOLE_QUOTE_PUSH.md](docs/SUBSCRIBE_WHOLE_QUOTE_PUSH.md) — 全推行情订阅推送机制设计
|
|
@@ -426,10 +426,15 @@ python -m bigqmt_signal_trader.qmt_launcher restart --dir "D:\国金证券QMT交
|
|
|
426
426
|
| `exe` | 直接起 `XtItClient.exe`,靠终端自身恢复会话 | 否 |
|
|
427
427
|
| `login` | 起 exe 后向登录框输入账号密码 | 是,需 pywin32 |
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
`
|
|
431
|
-
|
|
432
|
-
|
|
429
|
+
**`login` 模式需要未锁屏的交互式桌面。** 它用的是 `keybd_event` / `mouse_event`
|
|
430
|
+
物理输入(经 ctypes),不是 `SendMessage`——消息式输入投不到 Qt 对话框的焦点控件上,
|
|
431
|
+
当别的窗口在前台时会静默失败,什么也不输入。物理输入要求对话框在最前,所以启动前会
|
|
432
|
+
先把它置顶并核验;锁屏或 RDP 注销的会话直接抛 `QmtLauncherError` 而不是打一半密码。
|
|
433
|
+
|
|
434
|
+
> 需要**无人值守定时重启**的话,用 `linkmini` / `bat` / `exe` 三种模式,它们不碰登录框,
|
|
435
|
+
> 锁屏也能跑。只有 `login` 受这条限制。
|
|
436
|
+
|
|
437
|
+
密码从环境变量 `BIGQMT_LOGIN_USER` / `BIGQMT_LOGIN_PASSWORD` 读,不走命令行参数——argv
|
|
433
438
|
对同机任何进程可见。
|
|
434
439
|
|
|
435
440
|
两个设计要点:
|
|
@@ -625,6 +630,67 @@ credit_positions = credit_trader.query_stock_positions(credit_acc)
|
|
|
625
630
|
|
|
626
631
|
---
|
|
627
632
|
|
|
633
|
+
## 与 MiniQMT 的兼容性对照
|
|
634
|
+
|
|
635
|
+
本项目的目标是让照着 MiniQMT (`xtquant`) 写的代码不改就能跑。下表列出**返回值契约**——类型不对不会报错,只会让判断悄悄反过来,所以单独列出来。
|
|
636
|
+
|
|
637
|
+
### 返回值:与 MiniQMT 一致
|
|
638
|
+
|
|
639
|
+
| 接口 | 返回 | 说明 |
|
|
640
|
+
|---|---|---|
|
|
641
|
+
| `order_stock()` | `int` | 成功为正数,失败 `-1` |
|
|
642
|
+
| `order_stock_async()` | `int` | 请求序号 seq,结果走 `on_order_stock_async_response` |
|
|
643
|
+
| `cancel_order_stock()` | `int` | **`0` 成功,`-1` 失败**(不是 True/False) |
|
|
644
|
+
| `cancel_order_stock_sysid()` | `int` | 同上 |
|
|
645
|
+
| `cancel_order_stock_async()` | `int` | seq |
|
|
646
|
+
| `connect()` / `start()` | `int` | `0` 成功 |
|
|
647
|
+
| `subscribe()` / `unsubscribe()` | `int` | `0` 成功 |
|
|
648
|
+
| `query_stock_asset()` | 对象 | `.cash` / `.total_asset` 等属性 |
|
|
649
|
+
| `query_stock_positions()` | `list[对象]` | |
|
|
650
|
+
| `query_stock_orders()` / `query_stock_trades()` | `list[对象]` | |
|
|
651
|
+
| `subscribe_quote()` / `subscribe_whole_quote()` | `int` | 订阅号,传给 `unsubscribe_quote()` |
|
|
652
|
+
| `get_full_tick()` | `dict` | `{code: {...}}` |
|
|
653
|
+
| `get_market_data_ex()` | `dict[str, DataFrame]` | |
|
|
654
|
+
|
|
655
|
+
### 订单号:既是 int 也是 str
|
|
656
|
+
|
|
657
|
+
MiniQMT 的 `order_id` 是 int(委托编号),`order_sysid` 是 str(柜台合同编号)。大 QMT **没有前者**——`get_trade_detail_data` 只给 `m_strOrderSysID` 这个字符串。
|
|
658
|
+
|
|
659
|
+
所以这里的 `order_id` 是一个 int 子类,两种形态同时成立:
|
|
660
|
+
|
|
661
|
+
```python
|
|
662
|
+
order_id = xt_trader.order_stock(acc, "600000.SH", 23, 100, 11, 10.0, "s", "")
|
|
663
|
+
|
|
664
|
+
isinstance(order_id, int) # True —— MiniQMT 写法照常
|
|
665
|
+
order_id > 0 # True
|
|
666
|
+
order_id == -1 # 失败时才 True
|
|
667
|
+
|
|
668
|
+
str(order_id) # '合同编号' —— 券商给的原始字符串
|
|
669
|
+
xt_trader.cancel_order_stock(acc, order_id) # 撤单送回的是原始字符串
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
合同编号是纯数字时(多数券商),int 值就是那个数字,两种形态完全一致;不是纯数字时 int 是一个稳定的正数替身,而撤单、打印用的仍是真实编号。
|
|
673
|
+
|
|
674
|
+
把 order_id 存进数据库再取出来(变成普通 int)也能撤单——客户端记着最近 4096 个的对应关系。想要字符串就用 `.order_sysid`,它一直是 str。
|
|
675
|
+
|
|
676
|
+
同样的规则适用于 `XtOrder.order_id`、`XtTrade.order_id`,以及回调对象 `XtOrderError` / `XtCancelError` / `XtOrderResponse` 里的 `order_id`。
|
|
677
|
+
|
|
678
|
+
### 行为差异(不是返回值,但会咬人)
|
|
679
|
+
|
|
680
|
+
| 项目 | MiniQMT | 本项目 |
|
|
681
|
+
|---|---|---|
|
|
682
|
+
| `get_full_tick(["SH"])` | 全市场 | **默认只取股票**(1.08s);要全部传 `types=["all"]`(7.4s,含地方债等 26744 只) |
|
|
683
|
+
| `get_instrument_detail()` 查不到 | `None` | `{}`(两者都是 falsy,`if not detail` 通用) |
|
|
684
|
+
| `download_history_data()` | 无返回 | 返回 `{"finished": n, "total": n}`(多给的信息,可忽略) |
|
|
685
|
+
| 账户类型 | `StockAccount(id, "CREDIT")` 即可 | 还需服务端 `BIGQMT_ACCOUNT_TYPE = "CREDIT"`,**客户端的类型不会传到服务端** |
|
|
686
|
+
| 委托类型常量 | `xtconstant.order_type` | 内部会翻译成 `passorder` 的 opType(两套编号,专项两融 40–45 → 70–75) |
|
|
687
|
+
|
|
688
|
+
### 本项目的扩展(MiniQMT 没有)
|
|
689
|
+
|
|
690
|
+
这些不是兼容项,是多出来的:`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()`。
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
628
694
|
## 环境要求与依赖安装
|
|
629
695
|
|
|
630
696
|
本系统分两部分,各自需要自己的 Python 环境和依赖:
|
|
@@ -708,6 +774,8 @@ cd D:\国金证券QMT交易端
|
|
|
708
774
|
|
|
709
775
|
## 快速开始
|
|
710
776
|
|
|
777
|
+
> 第一次部署、只想要最短路径?直接看 [docs/DEPLOY_QUICKSTART.md](docs/DEPLOY_QUICKSTART.md)(单账号五步跑通 + 常见问题表)。
|
|
778
|
+
|
|
711
779
|
> 前置:客户端已按上面「A. 客户端」装好包;服务端按「B. 服务端」装好所选传输的依赖。下面是从零跑通整套流程的步骤。
|
|
712
780
|
>
|
|
713
781
|
> 只想把配置生成出来的话,跑 [`bigqmt-init`](#配置向导bigqmt-init) 即可——第 3 步的两份配置它会替你写好,选单文件部署还会顺带把构建也做了。
|
|
@@ -1196,6 +1264,8 @@ python qmt-trader/scripts/qmt.py buy 600000.SH 100 --price 7.50 --dry-run
|
|
|
1196
1264
|
## 相关文档
|
|
1197
1265
|
|
|
1198
1266
|
- [CHANGELOG.md](CHANGELOG.md) — **版本变更记录**(新增/修复/变更)
|
|
1267
|
+
- [docs/DEPLOY_QUICKSTART.md](docs/DEPLOY_QUICKSTART.md) — **单账号部署快速开始**(最短路径 + 部署期常见问题表)
|
|
1268
|
+
- [docs/LATENCY_REPORT.md](docs/LATENCY_REPORT.md) — **延迟测试报告**(传输层对比、FormulaServer 直连、下单链路、方法论)
|
|
1199
1269
|
- [docs/RPC_API_REFERENCE.md](docs/RPC_API_REFERENCE.md) — **全部 RPC 方法参考**(参数、返回值、别名、大 QMT 能力边界)
|
|
1200
1270
|
- [docs/FORMULA_SERVER_FASTPATH.md](docs/FORMULA_SERVER_FASTPATH.md) — FormulaServer(58600) 直连快速路径:协议、映射表、能力边界与回退行为
|
|
1201
1271
|
- [docs/SUBSCRIBE_WHOLE_QUOTE_PUSH.md](docs/SUBSCRIBE_WHOLE_QUOTE_PUSH.md) — 全推行情订阅推送机制设计
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "xtquant-big-convert"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Big QMT RPC bridge and MiniQMT-compatible adapter layer (redis/zmq/mysql transports)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -5,6 +5,8 @@ The passorder signature follows src/api/qmt_jq_trade.
|
|
|
5
5
|
|
|
6
6
|
import hashlib
|
|
7
7
|
|
|
8
|
+
from xtquant import xtconstant as _xtconstant
|
|
9
|
+
|
|
8
10
|
from ..code_utils import normalize_stock_code
|
|
9
11
|
from ..exec_events import date_time_seconds
|
|
10
12
|
from ..models import CancelResult, OrderSnapshot, OrderSubmitResult, SignalAction, TradeSnapshot
|
|
@@ -21,6 +23,75 @@ PRICE_TYPE_ALIASES = {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
# MiniQMT's order_type (xtconstant) and passorder's opType are two different
|
|
27
|
+
# numberings. They agree on 27-32 and diverge on the special-margin family:
|
|
28
|
+
#
|
|
29
|
+
# meaning xtconstant passorder opType (API ref 10.1)
|
|
30
|
+
# 融资买入 .. 直接还款 27-32 27-32 same
|
|
31
|
+
# 担保品买入 / 卖出 -- (CREDIT_BUY/SELL) 33 / 34
|
|
32
|
+
# 专项两融 40-45 70-75 DIFFERENT
|
|
33
|
+
#
|
|
34
|
+
# Sending 40 through unchanged would reach passorder as "期货组合开多", so the
|
|
35
|
+
# translation is not optional. Values are taken from xtconstant by NAME: PR #88
|
|
36
|
+
# asserted them as literals and encoded the same mistake in its tests, which is
|
|
37
|
+
# why they passed while the mapping was wrong.
|
|
38
|
+
_XC = _xtconstant
|
|
39
|
+
|
|
40
|
+
CREDIT_OPTYPE_BY_ORDER_TYPE = {
|
|
41
|
+
_XC.CREDIT_FIN_BUY: 27, # 融资买入
|
|
42
|
+
_XC.CREDIT_SLO_SELL: 28, # 融券卖出
|
|
43
|
+
_XC.CREDIT_BUY_SECU_REPAY: 29, # 买券还券
|
|
44
|
+
_XC.CREDIT_DIRECT_SECU_REPAY: 30, # 直接还券
|
|
45
|
+
_XC.CREDIT_SELL_SECU_REPAY: 31, # 卖券还款
|
|
46
|
+
_XC.CREDIT_DIRECT_CASH_REPAY: 32, # 直接还款
|
|
47
|
+
_XC.CREDIT_FIN_BUY_SPECIAL: 70, # 专项融资买入
|
|
48
|
+
_XC.CREDIT_SLO_SELL_SPECIAL: 71, # 专项融券卖出
|
|
49
|
+
_XC.CREDIT_BUY_SECU_REPAY_SPECIAL: 72, # 专项买券还券
|
|
50
|
+
_XC.CREDIT_DIRECT_SECU_REPAY_SPECIAL: 73, # 专项直接还券
|
|
51
|
+
_XC.CREDIT_SELL_SECU_REPAY_SPECIAL: 74, # 专项卖券还款
|
|
52
|
+
_XC.CREDIT_DIRECT_CASH_REPAY_SPECIAL: 75, # 专项直接还款
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Which side of the book each one is, for bookkeeping only -- the opType above
|
|
56
|
+
# is what actually goes to passorder. Repayment operations that move securities
|
|
57
|
+
# are classified by what they do to the holding.
|
|
58
|
+
_CREDIT_BUY_SIDE = frozenset({
|
|
59
|
+
_XC.CREDIT_FIN_BUY, _XC.CREDIT_BUY_SECU_REPAY,
|
|
60
|
+
_XC.CREDIT_FIN_BUY_SPECIAL, _XC.CREDIT_BUY_SECU_REPAY_SPECIAL,
|
|
61
|
+
})
|
|
62
|
+
_CREDIT_SELL_SIDE = frozenset({
|
|
63
|
+
_XC.CREDIT_SLO_SELL, _XC.CREDIT_SELL_SECU_REPAY,
|
|
64
|
+
_XC.CREDIT_DIRECT_SECU_REPAY,
|
|
65
|
+
_XC.CREDIT_SLO_SELL_SPECIAL, _XC.CREDIT_SELL_SECU_REPAY_SPECIAL,
|
|
66
|
+
_XC.CREDIT_DIRECT_SECU_REPAY_SPECIAL,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def credit_action_of(order_type):
|
|
71
|
+
"""BUY / SELL for a credit order_type, or None if it is not one.
|
|
72
|
+
|
|
73
|
+
直接还款 (32 / 45) moves cash rather than securities, so it has no side;
|
|
74
|
+
callers must pass an action for it explicitly.
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
value = int(order_type)
|
|
78
|
+
except (TypeError, ValueError):
|
|
79
|
+
return None
|
|
80
|
+
if value in _CREDIT_BUY_SIDE:
|
|
81
|
+
return SignalAction.BUY.value
|
|
82
|
+
if value in _CREDIT_SELL_SIDE:
|
|
83
|
+
return SignalAction.SELL.value
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def credit_optype_of(order_type):
|
|
88
|
+
"""passorder opType for a MiniQMT credit order_type, or None."""
|
|
89
|
+
try:
|
|
90
|
+
return CREDIT_OPTYPE_BY_ORDER_TYPE.get(int(order_type))
|
|
91
|
+
except (TypeError, ValueError):
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
24
95
|
def _action_from_offset_flag(offset_flag):
|
|
25
96
|
return SignalAction.BUY.value if int(offset_flag or 0) == 48 else SignalAction.SELL.value
|
|
26
97
|
|
|
@@ -166,7 +237,14 @@ class BigQmtOrderGateway:
|
|
|
166
237
|
def submit(self, request):
|
|
167
238
|
passorder = self._require_passorder()
|
|
168
239
|
action = str(request.action).upper()
|
|
169
|
-
|
|
240
|
+
credit_optype = credit_optype_of(getattr(request, "order_type", None))
|
|
241
|
+
if credit_optype is not None:
|
|
242
|
+
# A credit operation carries more than a side: mapping it back to
|
|
243
|
+
# BUY/SELL would turn 融资买入 into an ordinary buy, which is the
|
|
244
|
+
# bug behind issue #103 -- worse than the rejection it replaced,
|
|
245
|
+
# because it places a real but different order.
|
|
246
|
+
op_type = credit_optype
|
|
247
|
+
elif action == SignalAction.BUY.value:
|
|
170
248
|
op_type = 23
|
|
171
249
|
elif action == SignalAction.SELL.value:
|
|
172
250
|
op_type = 24
|
|
@@ -329,7 +329,12 @@ class OrderRequest:
|
|
|
329
329
|
price_type,
|
|
330
330
|
strategy_name,
|
|
331
331
|
remark="",
|
|
332
|
+
order_type=None,
|
|
332
333
|
):
|
|
334
|
+
# MiniQMT-style order_type (xtconstant). Only set for operations a
|
|
335
|
+
# BUY/SELL action cannot express -- credit financing, repayment and the
|
|
336
|
+
# special-margin family. None means an ordinary stock order.
|
|
337
|
+
self.order_type = order_type
|
|
333
338
|
self.signal_id = signal_id
|
|
334
339
|
self.account_id = account_id
|
|
335
340
|
self.action = action
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""An order id that is an int for MiniQMT and a string for the broker.
|
|
3
|
+
|
|
4
|
+
MiniQMT's ``order_stock`` returns an int: a positive 委托编号, or ``-1`` when the
|
|
5
|
+
submit failed. Big QMT has no such number -- ``get_trade_detail_data`` hands
|
|
6
|
+
back ``m_strOrderSysID``, the broker's 合同编号, which is a string. This bridge
|
|
7
|
+
was returning that string straight through (issue #113), so every caller
|
|
8
|
+
written against MiniQMT broke, and broke quietly:
|
|
9
|
+
|
|
10
|
+
order_id = trader.order_stock(...)
|
|
11
|
+
if order_id == -1: # never true: "-1" != -1, so a rejected order
|
|
12
|
+
... # reads as success
|
|
13
|
+
if order_id > 0: # TypeError: '>' not supported between str and int
|
|
14
|
+
|
|
15
|
+
``OrderId`` is both. It *is* an int, so comparisons, ``isinstance`` and
|
|
16
|
+
arithmetic behave the way MiniQMT callers expect; and it carries the exact
|
|
17
|
+
broker string on ``.order_sys_id``, so a cancel sends back what QMT issued
|
|
18
|
+
rather than a number we invented -- including ids that are not numeric at all.
|
|
19
|
+
|
|
20
|
+
Where the 合同编号 is a plain positive integer (the usual case) the int value is
|
|
21
|
+
that same number and the two representations agree exactly. Where it is not,
|
|
22
|
+
the int is a stable surrogate derived from the string: still positive, so
|
|
23
|
+
``> 0`` keeps separating success from failure, while ``str()`` and the cancel
|
|
24
|
+
path use the real id.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import zlib
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Positive and comfortably inside 32 bits, so the value survives a trip through
|
|
31
|
+
# JSON, a database column, or anything else that is unhappy with big integers.
|
|
32
|
+
_SURROGATE_MASK = 0x3FFFFFFF
|
|
33
|
+
|
|
34
|
+
_DIGITS = frozenset("0123456789")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _is_ascii_digits(text):
|
|
38
|
+
# str.isdigit() is True for '123' and other unicode digit forms, which
|
|
39
|
+
# int() then happily parses into something the broker never issued.
|
|
40
|
+
return bool(text) and all(char in _DIGITS for char in text)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def int_value_of(order_sys_id):
|
|
44
|
+
"""The int an ``OrderId`` for this 合同编号 should carry.
|
|
45
|
+
|
|
46
|
+
Returns 0 for an empty id -- there is no order, so there is no number.
|
|
47
|
+
"""
|
|
48
|
+
text = ("" if order_sys_id is None else str(order_sys_id)).strip()
|
|
49
|
+
if not text:
|
|
50
|
+
return 0
|
|
51
|
+
if _is_ascii_digits(text):
|
|
52
|
+
value = int(text)
|
|
53
|
+
if value > 0:
|
|
54
|
+
return value
|
|
55
|
+
# Alphanumeric 合同编号, or a leading-zero form whose int would not round
|
|
56
|
+
# trip. Derive a stable surrogate instead of guessing a number: same string
|
|
57
|
+
# gives the same value in any process, so a caller comparing ids across a
|
|
58
|
+
# restart still matches. `or 1` keeps it strictly positive.
|
|
59
|
+
return (zlib.crc32(text.encode("utf-8")) & _SURROGATE_MASK) or 1
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class OrderId(int):
|
|
63
|
+
"""``int(order_id)`` for MiniQMT, ``str(order_id)`` for the broker."""
|
|
64
|
+
|
|
65
|
+
# No __slots__: CPython rejects a nonempty __slots__ on an int subclass
|
|
66
|
+
# (variable-length builtin), so the instance carries an ordinary __dict__.
|
|
67
|
+
|
|
68
|
+
def __new__(cls, order_sys_id, value=None):
|
|
69
|
+
text = "" if order_sys_id is None else str(order_sys_id)
|
|
70
|
+
if value is None:
|
|
71
|
+
value = int_value_of(text)
|
|
72
|
+
obj = int.__new__(cls, value)
|
|
73
|
+
obj.order_sys_id = text
|
|
74
|
+
return obj
|
|
75
|
+
|
|
76
|
+
def __str__(self):
|
|
77
|
+
# int.__str__ is object.__str__ on py3, which routes back through the
|
|
78
|
+
# __repr__ below; int.__repr__ is the one that prints the number.
|
|
79
|
+
return self.order_sys_id or int.__repr__(self)
|
|
80
|
+
|
|
81
|
+
def __format__(self, spec):
|
|
82
|
+
# "{}".format(oid) and f"{oid}" go through __format__, not __str__, and
|
|
83
|
+
# int.__format__ with an empty spec would print the surrogate. Anything
|
|
84
|
+
# with an actual spec ("%05d"-style) is asking for the number.
|
|
85
|
+
if not spec:
|
|
86
|
+
return self.__str__()
|
|
87
|
+
return int.__format__(self, spec)
|
|
88
|
+
|
|
89
|
+
def __repr__(self):
|
|
90
|
+
return "OrderId(%r, %d)" % (self.order_sys_id, int(self))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def order_sys_id_of(value):
|
|
94
|
+
"""Best available broker string for something a caller handed back.
|
|
95
|
+
|
|
96
|
+
Accepts an ``OrderId``, a bare string, or the int a caller round-tripped
|
|
97
|
+
through JSON and lost the string half of.
|
|
98
|
+
"""
|
|
99
|
+
carried = getattr(value, "order_sys_id", None)
|
|
100
|
+
if carried:
|
|
101
|
+
return str(carried)
|
|
102
|
+
if value is None:
|
|
103
|
+
return ""
|
|
104
|
+
return str(value)
|
{xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/qmt_launcher.py
RENAMED
|
@@ -159,6 +159,26 @@ def is_qmt_running(install_dir):
|
|
|
159
159
|
return bool(find_qmt_processes(install_dir))
|
|
160
160
|
|
|
161
161
|
|
|
162
|
+
def session_is_locked():
|
|
163
|
+
"""True when the interactive session is at the Windows lock screen.
|
|
164
|
+
|
|
165
|
+
Login automation is impossible while locked (physical input lands on the
|
|
166
|
+
lock screen), so callers can check before closing a running terminal.
|
|
167
|
+
Heuristic: the foreground window is the lock screen's CoreWindow.
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
import ctypes
|
|
171
|
+
|
|
172
|
+
import win32gui
|
|
173
|
+
|
|
174
|
+
hwnd = ctypes.windll.user32.GetForegroundWindow()
|
|
175
|
+
title = win32gui.GetWindowText(hwnd) or ""
|
|
176
|
+
cls = win32gui.GetClassName(hwnd)
|
|
177
|
+
return cls == "Windows.UI.Core.CoreWindow" and "锁屏" in title
|
|
178
|
+
except Exception:
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
|
|
162
182
|
# ------------------------------------------------------------------ readiness
|
|
163
183
|
def port_is_listening(port=DEFAULT_READY_PORT, host="127.0.0.1", timeout=1.0):
|
|
164
184
|
sock = socket.socket()
|
|
@@ -487,6 +507,13 @@ def restart_qmt(install_dir, settle_seconds=5.0, **open_kwargs):
|
|
|
487
507
|
after the process dies, and the ZMQ transport binds its configured port
|
|
488
508
|
exactly (no scanning), so restarting too eagerly fails the rebind.
|
|
489
509
|
"""
|
|
510
|
+
mode = str(open_kwargs.get("mode") or "auto")
|
|
511
|
+
if mode in ("login", "auto") and session_is_locked():
|
|
512
|
+
raise QmtLauncherError(
|
|
513
|
+
"interactive session is locked; the login dialog cannot be automated "
|
|
514
|
+
"now. Unlock the session first, or restart without closing "
|
|
515
|
+
"(the terminal would sit at the login dialog with trading down)."
|
|
516
|
+
)
|
|
490
517
|
closed = close_qmt(install_dir)
|
|
491
518
|
if closed:
|
|
492
519
|
time.sleep(settle_seconds)
|
{xtquant_big_convert-0.3.0 → xtquant_big_convert-0.3.2}/src/bigqmt_signal_trader/redis_rpc.py
RENAMED
|
@@ -32,6 +32,7 @@ RPC_REVISION = "20260715-execution-snapshot-v1"
|
|
|
32
32
|
READ_METHODS = {
|
|
33
33
|
"ping",
|
|
34
34
|
"get_deployment_info",
|
|
35
|
+
"probe_capabilities",
|
|
35
36
|
"get_ticks",
|
|
36
37
|
"get_instrument",
|
|
37
38
|
"get_instrument_type",
|
|
@@ -340,6 +341,29 @@ def to_jsonable(value):
|
|
|
340
341
|
}
|
|
341
342
|
except Exception:
|
|
342
343
|
return str(value)
|
|
344
|
+
# pandas Panel (3-D). QMT ships pandas 0.22, where get_financial_data with
|
|
345
|
+
# several stocks AND several dates still returns one. A Panel has no
|
|
346
|
+
# .columns and no .index, so it falls past the DataFrame and Series
|
|
347
|
+
# branches all the way to the __dict__ fallback -- and vars(panel) is
|
|
348
|
+
# {'_data': ..., 'is_copy': None}, which the underscore filter reduces to
|
|
349
|
+
# {'is_copy': None}. That is exactly what callers got back (issue #115):
|
|
350
|
+
# not an error, just the one public attribute the object happened to have.
|
|
351
|
+
#
|
|
352
|
+
# pandas removed Panel in 1.0, so the client cannot rebuild one even if we
|
|
353
|
+
# sent the axes. Send a DataFrame per item instead; it arrives as a dict.
|
|
354
|
+
if (hasattr(value, "major_axis") and hasattr(value, "minor_axis")
|
|
355
|
+
and hasattr(value, "items") and not isinstance(value, dict)):
|
|
356
|
+
try:
|
|
357
|
+
labels = [str(item) for item in value.items]
|
|
358
|
+
return {
|
|
359
|
+
"__bigqmt_type__": "Panel",
|
|
360
|
+
"items": labels,
|
|
361
|
+
"major_axis": [str(item) for item in value.major_axis],
|
|
362
|
+
"minor_axis": [str(item) for item in value.minor_axis],
|
|
363
|
+
"data": {str(item): to_jsonable(value[item]) for item in value.items},
|
|
364
|
+
}
|
|
365
|
+
except Exception:
|
|
366
|
+
return str(value)
|
|
343
367
|
if hasattr(value, "tolist") and not isinstance(value, (str, bytes, bytearray)):
|
|
344
368
|
try:
|
|
345
369
|
return to_jsonable(value.tolist())
|
|
@@ -527,6 +551,67 @@ class BigQmtRpcHandlers:
|
|
|
527
551
|
pass
|
|
528
552
|
return info
|
|
529
553
|
|
|
554
|
+
# probe 时检查的关键 QMT 运行时全局函数(存在与否决定对应能力可用性)。
|
|
555
|
+
_PROBE_QMT_GLOBALS = (
|
|
556
|
+
"passorder", "cancel", "get_trade_detail_data",
|
|
557
|
+
"download_history_data", "download_history_data2", "down_history_data",
|
|
558
|
+
"get_history_trade_detail_data", "get_value_by_order_id", "get_last_order_id",
|
|
559
|
+
"get_ipo_data", "get_new_purchase_limit",
|
|
560
|
+
"get_assure_contract", "get_enable_short_contract",
|
|
561
|
+
"get_unclosed_compacts", "get_closed_compacts", "get_debt_contract",
|
|
562
|
+
"get_option_subject_position", "get_comb_option", "get_hkt_exchange_rate",
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
# probe 时抽查的 ContextInfo 方法。
|
|
566
|
+
_PROBE_CONTEXT_METHODS = (
|
|
567
|
+
"get_full_tick", "get_market_data_ex", "get_market_data", "get_local_data",
|
|
568
|
+
"subscribe_quote", "subscribe_whole_quote", "unsubscribe_quote",
|
|
569
|
+
"get_trading_dates", "get_financial_data", "get_stock_list_in_sector",
|
|
570
|
+
"do_back_test", "get_trade_detail_data",
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
def _handle_probe_capabilities(self, params):
|
|
574
|
+
"""只读能力探测:当前部署暴露了哪些 QMT callable。
|
|
575
|
+
|
|
576
|
+
部署后跑一次就能回答「这台券商 QMT 缺什么」——只读调用,不触发任何
|
|
577
|
+
委托。返回三部分:运行时全局函数绑定情况、ContextInfo 方法存在性、
|
|
578
|
+
信用接口只读探测(调用一次看是否报错/返回行数)。
|
|
579
|
+
"""
|
|
580
|
+
info = {
|
|
581
|
+
"account_id": self.account_id,
|
|
582
|
+
"allow_order_methods": self.allow_order_methods,
|
|
583
|
+
"settle_orders_inline": self.settle_orders_inline,
|
|
584
|
+
"order_settle_timeout_seconds": self.order_settle_timeout_seconds,
|
|
585
|
+
"qmt_globals": {},
|
|
586
|
+
"contextinfo_methods": {},
|
|
587
|
+
"credit_probe": {},
|
|
588
|
+
"server_time": _dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
589
|
+
}
|
|
590
|
+
for name in self._PROBE_QMT_GLOBALS:
|
|
591
|
+
info["qmt_globals"][name] = callable(self.qmt_api.get(name))
|
|
592
|
+
context_info = getattr(self.market_data, "context_info", None)
|
|
593
|
+
for name in self._PROBE_CONTEXT_METHODS:
|
|
594
|
+
info["contextinfo_methods"][name] = callable(getattr(context_info, name, None))
|
|
595
|
+
# 信用接口只读探测:不存在的全局直接标 unavailable;存在的真调一次,
|
|
596
|
+
# 记录是否报错和返回行数(担保品/融券标的可能很多行,只计数)。
|
|
597
|
+
for name in ("get_assure_contract", "get_enable_short_contract",
|
|
598
|
+
"get_unclosed_compacts", "get_debt_contract"):
|
|
599
|
+
func = self.qmt_api.get(name)
|
|
600
|
+
if not callable(func):
|
|
601
|
+
info["credit_probe"][name] = {"available": False}
|
|
602
|
+
continue
|
|
603
|
+
try:
|
|
604
|
+
rows = func(self.account_id) or []
|
|
605
|
+
info["credit_probe"][name] = {
|
|
606
|
+
"available": True, "ok": True, "rows": len(rows),
|
|
607
|
+
}
|
|
608
|
+
except Exception as exc:
|
|
609
|
+
info["credit_probe"][name] = {
|
|
610
|
+
"available": True, "ok": False,
|
|
611
|
+
"error": "%s: %s" % (exc.__class__.__name__, exc),
|
|
612
|
+
}
|
|
613
|
+
return info
|
|
614
|
+
|
|
530
615
|
# ------------------------------------------------------------------
|
|
531
616
|
# 全推行情订阅控制(引用计数共享 ContextInfo.subscribe_whole_quote)。
|
|
532
617
|
# 数据本身走推送通道;这里只负责订阅生命周期 + 心跳。
|
|
@@ -996,13 +1081,29 @@ class BigQmtRpcHandlers:
|
|
|
996
1081
|
action = str(params.get("action") or "").upper()
|
|
997
1082
|
if action:
|
|
998
1083
|
return action
|
|
999
|
-
|
|
1084
|
+
raw = params.get("order_type")
|
|
1085
|
+
order_type = str(raw or "").upper()
|
|
1000
1086
|
if order_type in BUY_ORDER_TYPES:
|
|
1001
1087
|
return "BUY"
|
|
1002
1088
|
if order_type in SELL_ORDER_TYPES:
|
|
1003
1089
|
return "SELL"
|
|
1090
|
+
# Credit operations carry their side in the type itself (issue #103).
|
|
1091
|
+
# 直接还款 moves cash rather than securities and has no side, so it
|
|
1092
|
+
# still needs an explicit action rather than being guessed at.
|
|
1093
|
+
credit = _credit_action_of(raw)
|
|
1094
|
+
if credit:
|
|
1095
|
+
return credit
|
|
1096
|
+
if _credit_optype_of(raw) is not None:
|
|
1097
|
+
raise ValueError(
|
|
1098
|
+
"order_type %s has no implicit buy/sell side; pass action "
|
|
1099
|
+
"explicitly" % raw)
|
|
1004
1100
|
raise ValueError("action or order_type is required")
|
|
1005
1101
|
|
|
1102
|
+
def _credit_order_type_from_params(self, params):
|
|
1103
|
+
"""The MiniQMT order_type to forward, when it is a credit operation."""
|
|
1104
|
+
raw = params.get("order_type")
|
|
1105
|
+
return raw if _credit_optype_of(raw) is not None else None
|
|
1106
|
+
|
|
1006
1107
|
def _handle_submit_order(self, params):
|
|
1007
1108
|
if self.order_gateway is None:
|
|
1008
1109
|
raise RuntimeError("order_gateway is not configured")
|
|
@@ -1021,6 +1122,7 @@ class BigQmtRpcHandlers:
|
|
|
1021
1122
|
price_type=params.get("price_type") or "LIMIT",
|
|
1022
1123
|
strategy_name=str(params.get("strategy_name") or "bigqmt_rpc"),
|
|
1023
1124
|
remark=order_tag,
|
|
1125
|
+
order_type=self._credit_order_type_from_params(params),
|
|
1024
1126
|
)
|
|
1025
1127
|
if request.action not in ("BUY", "SELL"):
|
|
1026
1128
|
raise ValueError("action must be BUY or SELL")
|
|
@@ -1262,6 +1364,24 @@ def _bool_value(value, default=False):
|
|
|
1262
1364
|
return str(value).strip().lower() in ("1", "true", "yes", "y", "on")
|
|
1263
1365
|
|
|
1264
1366
|
|
|
1367
|
+
def _credit_action_of(order_type):
|
|
1368
|
+
try:
|
|
1369
|
+
from bigqmt_signal_trader.adapters.order_bigqmt import credit_action_of
|
|
1370
|
+
|
|
1371
|
+
return credit_action_of(order_type)
|
|
1372
|
+
except Exception:
|
|
1373
|
+
return None
|
|
1374
|
+
|
|
1375
|
+
|
|
1376
|
+
def _credit_optype_of(order_type):
|
|
1377
|
+
try:
|
|
1378
|
+
from bigqmt_signal_trader.adapters.order_bigqmt import credit_optype_of
|
|
1379
|
+
|
|
1380
|
+
return credit_optype_of(order_type)
|
|
1381
|
+
except Exception:
|
|
1382
|
+
return None
|
|
1383
|
+
|
|
1384
|
+
|
|
1265
1385
|
def _deployed_version():
|
|
1266
1386
|
"""Version of the bridge actually running here, or "" if unknown."""
|
|
1267
1387
|
try:
|