Files
one_divine_lot/docs/05-需求池/R-007.md
T
kyugao 8caeb769ed fix: 佣金按委托级合并计费(分笔成交只收一次佣金)
- calcOrderFees 委托层合并计费:同一委托分笔成交按总金额算一次佣金(最低5元一次)
- mapTrade 保留原始成交字段,不再逐笔算费
- 前端聚合调用 calcOrderFees,展开明细只展示成交本身
- 验证:200股拆两笔佣金5元(非10元);两笔独立委托各5元
2026-09-01 13:25:37 +08:00

544 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# R-007 交易记录接入(订单与成交数据)· 需求与技术研究
> 需求状态:**起草**(2026-09-01 入池,需求与技术研究进行中;AI 提议提为「讨论中」,待老师确认)
> 更新日期:2026-09-01 来源:老师指令(2026-09-01
> 关联:docs/05-需求池/需求池索引.mdR-007)、docs/01-终极目标/终极目标.md(目标-006 交易复盘、目标-003 按策略监控市场)
> 前置研究:本文件已含第一轮技术研究(QMT Bridge 订单/成交 API 实测 + 现有架构集成分析),待确认问题 Q1-Q10 待老师逐条拍板
## 需求概述
在神之一手的**「交易记录」tab**(当前为占位)中接入 **QMT Bridge 的订单(当日委托)与成交(当日成交)数据**,实时展示当日委托与成交明细,支撑**交易复盘**(目标-006)与**按策略监控交易**(目标-003)。
- **订单数据**:当日委托列表(含委托状态、委托量、成交量、限价、时间等);
- **成交数据**:当日成交列表(含成交价、成交量、手续费、成交额、时间等);
- **展示位置**:现有「交易记录」tabconversation.viewid=odl-trade-recordstradeRecords 显隐开关已预置,当前渲染 PlaceholderTab 占位);
- **数据来源**:插件服务端直连 QMT Bridge REST(技术约束-003,不经过 MCP 层),复用现有统一数据源抽象(技术约束-001)。
## 现状分析
### 1. QMT Bridge 已具备订单/成交查询能力(API 面实测)
| 端点 | 方法 | 说明 | 过滤参数 |
|---|---|---|---|
| /trade/orders | GET | 当日委托列表(m_* 原始字段),账号为桥绑定账号(单账号策略) | code(客户端过滤,600519 或 600519.SH)、statusactive=排除已撤54/废单57,或数字状态码 48-57)、strategy_nameQMT 服务端按 passorder 策略名过滤) |
| /trade/trades | GET | 当日成交列表(m_* 原始字段) | 无(返回全部当日成交) |
**数据特性**:两类数据均由 QMT 策略线程(adjust)每 **300ms 缓存刷新**,HTTP 线程只读缓存——即数据本身是准实时的(约 300ms 延迟),服务端无需自建订阅,REST 轮询即可。
### 2. 数据结构(OpenAPI 规范 + 线上实测样本)
**Order(当日委托)核心字段**
| 字段 | 类型 | 说明 |
|---|---|---|
| m_strOrderSysID | string | 合同编号/委托号(撤单凭此号) |
| m_strInstrumentID / m_strInstrumentName | string | 证券代码(不带后缀)/ 名称 |
| m_nDirection | int | 操作标记(EEntrustBS**股票买卖永远是 48**,不表达方向,官方已注明;见 Q3 研究) |
| m_nOrderStatus | int | 委托状态码:48=未报 49=待报 50=已报(待成交) 51=已报待撤 52=部成待撤 53=部撤 54=已撤 55=部成 56=已成 57=废单 |
| m_nVolumeTotalOriginal / m_nVolumeTraded / m_nVolumeTotal | int | 委托数量(最初)/ 已成交量 / 剩余量 |
| m_dLimitPrice | number | 委托价格(限价单限价) |
| m_strInsertDate / m_strInsertTime | string | 委托日期(YYYYMMDD/ 时间(HHMMSS |
| m_dTradedPrice | number | 成交均价(股票) |
| m_strErrorMsg / m_strCancelInfo | string | 状态信息 / 废单原因 |
| m_strOptName | string | **操作名称(实测「限价卖出」,含方向语义,方向判定关键字段)** |
**Trade(当日成交)核心字段**
| 字段 | 类型 | 说明 |
|---|---|---|
| m_strTradeID | string | 成交编号 |
| m_strOrderSysID | string | 合同编号/委托号(与委托表同号,**用于关联委托↔成交**) |
| m_strInstrumentID / m_strInstrumentName | string | 证券代码(不带后缀)/ 名称 |
| m_nDirection | int | 操作标记(同订单,股票恒为 48,不判方向) |
| m_dPrice | number | 成交均价 |
| m_nVolume | int | 成交量(股) |
| m_strTradeDate / m_strTradeTime | string | 成交日期(YYYYMMDD/ 时间(HHMMSS |
| m_dCommission | number | 手续费 |
| m_dTradeAmount | number | 成交额 |
| m_strOptName | string | 操作名称(含方向语义) |
**线上实测样本**2026-09-01):当日 1 笔委托 + 1 笔成交(博纳影业 001330.SZ,限价卖出 200 股 @6.00,已成 m_nOrderStatus=56,手续费 0.852 元)。方向判定结论(官方 innerApi 字典核实):m_nDirection=48 是 EEntrustBS 的「买卖」操作标记(官方注明股票买卖永远是 48),**不表达方向**;真实方向在 m_nOffsetFlagEOffset_Flag_Type48=买入开仓/49=卖出平仓,实测 49 与「限价卖出」吻合);m_strOptName 为买卖标记中文展示。**方向判定应以 m_nOffsetFlag 为准 + m_strOptName 交叉验证(Q3 已解决)**。
### 2.1 QMT 桥返回字段全表(实测 2026-09-01
> 实调 QMT 桥 /trade/orders 与 /trade/trades 得到的完整字段。标注 ★ 的字段为**语义化映射应保留的核心字段**(股票场景),其余为期权/期货/人民币换算/融资融券等场景字段(股票时为空或默认值)。
> 字段体系:大 QMT 内置 APIinnerApi),枚举见 https://dict.thinktrader.net/innerApi/enum_constants.html
**当日委托(/trade/orders)字段表**
| 字段 | 类型 | 实测值 | 说明 | 映射 |
|---|---|---|---|---|
| m_strOrderSysID | string | 645009273 | 合同编号/委托号(唯一标识,撤单凭此号) | ★ |
| m_strOrderRef | string | SZ_645009273 | 委托引用号(带交易所前缀) | ★ |
| m_strAccountID | string | 8882874667 | 资金账号 | |
| m_strAccountKey | string | 2____10125____… | 账号组合键(内部用) | |
| m_strAccountName | string | (空) | 账号名称 | |
| m_strAccountRemark | string | (空) | 账号备注 | |
| m_strInstrumentID | string | 001330 | 证券代码(不带后缀) | ★ |
| m_strInstrumentName | string | 博纳影业 | 证券名称 | ★ |
| m_strExchangeID | string | SZ | 交易所(SH/SZ | ★ |
| m_strExchangeName | string | 深交所 | 交易所中文名 | |
| m_strUnderCode | string | (空) | 标的代码(衍生品用) | |
| m_strProductID / ProductName | string | (空) | 产品代码/名称(股票为空) | |
| m_nDirection | int | 48 | EEntrustBS 操作标记:**股票买卖恒为 48**(非方向) | |
| m_nOffsetFlag | int | 49 | **EOffset_Flag_Type48=买入开仓 / 49=卖出平仓(方向判定关键)** | ★ |
| m_nHedgeFlag | int | 49 | 投保标记(投机=49 | |
| m_nOrderPriceType | int | 50 | 价格类型(50=限价,enum_EBrokerPriceType | |
| m_eEntrustType | int | 48 | 委托类型(48=普通委托) | |
| m_strOptName | string | 限价卖出 | **买卖标记中文(方向展示/交叉验证)** | ★ |
| m_strOption / OrderParam / OrderStrategyType | string | (空) | 期权属性/委托参数/策略类型名 | |
| m_nOrderStrategyType / m_nOpType | int | 异常值 | 策略类型码/操作类型(无实际意义) | |
| m_nVolumeTotalOriginal | int | 200 | 委托数量(最初委托量) | ★ |
| m_nVolumeTraded | int | 200 | 已成交量 | ★ |
| m_nVolumeTotal | int | 0 | 剩余量(总委托-成交) | ★ |
| m_dLimitPrice | number | 6 | 委托价格(限价) | ★ |
| m_dTradedPrice | number | 6 | 成交均价 | ★ |
| m_dTradeAmount | number | 1200 | 成交金额 | ★ |
| m_dCancelAmount | number | 0 | 已撤数量 | |
| m_dOrderPriceRMB / TradeAmountRMB / ReferenceRate | number | 0 | 人民币换算/参考汇率(外币标的) | |
| m_dFrozenCommission / FrozenMargin / ShortOccupedMargin | number | 0 | 冻结手续费/保证金/卖空占用保证金 | |
| m_eCashgroupProp / m_eCoveredFlag | int | 48 / 0 | 资金组属性/遮盖标记 | |
| m_nOrderStatus | int | 56 | 委托状态:48未报 49待报 50已报 51已报待撤 52部成待撤 53部撤 54已撤 55部成 56已成 57废单 | ★ |
| m_nOrderSubmitStatus | int | 51 | 委托提交状态(51=已提交) | |
| m_strErrorMsg / m_strCancelInfo | string | (空) | 状态信息/废单原因 | |
| m_nErrorID | int | 2147483647 | 错误码(默认值=无错误) | |
| m_strInsertDate / m_strInsertTime | string | 20260901 / 101333 | 委托日期(YYYYMMDD/时间(HHMMSS | ★ |
| m_nStrategyID | int | 4322211943619263000 | 策略 IDpassorder 所属) | |
| m_strSource / m_strXTTrade | string | (空)/其他终端 | 委托来源/交易终端 | |
| m_nTaskId / FrontID / SessionID / Ref / GroupId | int | -1/2147483647 等 | 任务号/前置机/会话/引用/组号(无实际意义) | |
| m_strCompactNo / BrokerName / LocalInfo / Remark | string | (空) | 合约编号/券商/本地信息/备注 | |
| m_bEnable | bool | true | 是否有效 | |
| m_xtTag | null | null | 扩展标签 | |
**当日成交(/trade/trades)字段表**
| 字段 | 类型 | 实测值 | 说明 | 映射 |
|---|---|---|---|---|
| m_strTradeID | string | 0103000030812084 | 成交编号(唯一标识) | ★ |
| m_strOrderSysID | string | 645009273 | **合同编号/委托号(与委托表同号,关联委托↔成交)** | ★ |
| m_strOrderRef | string | SZ_645009273 | 委托引用号(带交易所前缀) | |
| m_strAccountID | string | 8882874667 | 资金账号 | |
| m_strAccountKey / AccountRemark | string | (空) | 账号组合键/备注 | |
| m_strInstrumentID | string | 001330 | 证券代码(不带后缀) | ★ |
| m_strInstrumentName | string | 博纳影业 | 证券名称 | ★ |
| m_strExchangeID / ExchangeName | string | SZ / 深交所 | 交易所 | ★ |
| m_strProductID / ProductName | string | (空) | 产品代码/名称(股票为空) | |
| m_nDirection | int | 48 | EEntrustBS 操作标记:**股票买卖恒为 48**(非方向) | |
| m_nOffsetFlag | int | 49 | **EOffset_Flag_Type48=买入开仓 / 49=卖出平仓(方向判定关键)** | ★ |
| m_nRealOffsetFlag | int | -1 | 实际操作类型(-1=未用) | |
| m_nHedgeFlag | int | 49 | 投保标记(投机=49 | |
| m_nOrderPriceType / m_eEntrustType | int | 50 / 48 | 价格类型/委托类型 | |
| m_eCoveredFlag / m_eFutureTradeType | int | 48 | 遮盖标记/期货交易类型 | |
| m_strOptName | string | 限价卖出 | **买卖标记中文(方向展示/交叉验证)** | ★ |
| m_strOperation | string | (空) | 操作描述 | |
| m_dPrice | number | 6 | 成交均价 | ★ |
| m_nVolume | int | 200 | 成交量(股) | ★ |
| m_dTradeAmount | number | 1200 | 成交金额 | ★ |
| m_dCommission | number | 0.852 | **佣金费率(万分之,2026-09-01 老师定)**0.852 = 万分之0.852 = 0.0000852(非金额);另有 m_dComssion 拼写变体同值 | ★ |
| m_dComssion | number | 0.852 | 手续费(拼写变体) | |
| m_dCloseProfit / m_nCloseTodayVolume | number/int | 0 | 平仓盈亏/平今量(期货用) | |
| m_dOrderPriceRMB / TradeAmountRMB / PriceRMB / ReferenceRate | number | 0 | 人民币换算/参考汇率 | |
| m_strTradeDate / m_strTradeTime | string | 20260901 / 101333 | 成交日期(YYYYMMDD/时间(HHMMSS | ★ |
| m_nStrategyID | int | 4322211943619263000 | 策略 IDpassorder 所属) | |
| m_nOrderStrategyType / m_strOrderStrategyType | int/string | 异常值/(空) | 委托策略类型(无实际意义) | |
| m_nTaskId / m_nGroupId / m_nRef | int | -1/2147483647/0 | 任务号/组号/引用(无实际意义) | |
| m_strSource / m_strXTTrade | string | (空)/其他终端 | 成交来源/交易终端 | |
| m_strCompactNo / LocalInfo / Remark | string | (空) | 合约编号/本地信息/备注 | |
| m_xtTag | null | null | 扩展标签 | |
**映射要点**:★ 字段共约 15 个/表,构成语义化模型(orderId/tradeId/code/name/exchange/direction/status/volumes/prices/amount/commission/time);m_nOffsetFlag+m_strOptName 双字段定方向;m_strOrderSysID 关联两表;**m_dCommission = 佣金费率(万分之),非金额(费用计算见「费用计算规则」)**。
**费用计算规则(2026-09-01 老师定,已实现 calcTradeFees**
| 费用项 | 计算 | 说明 |
|---|---|---|
| 佣金 | 成交额 × (m_dCommission ÷ 10000)**不足 5 元按 5 元** | 账号没免五,费率=万分之 m_dCommission(如 0.852 = 万0.852 |
| 印花税 | 成交额 × 万分之5(0.05% | **仅卖出**收取(2023-08-28 起标准,单边) |
| 过户费 | 成交额 × 万分之0.1(0.01‰) | **双向**收取,无最低限额(2022-04-29 起标准,中登收取,老师确认) |
| 总费用 | 佣金 + 印花税 + 过户费 | **委托级合并计费**:同一委托分笔成交按总金额算一次佣金;多笔独立委托各自算(2026-09-01 老师修正) |
### 2.2 Sample Data(真实数据样本,2026-09-01 实调)
> 实调 QMT 桥 /trade/orders 与 /trade/trades 获取的**当日真实数据**(账号 8882874667,交易日期 20260901)。
> 当日共 1 笔委托 + 1 笔成交(博纳影业 001330.SZ 限价卖出 200 股 @6.00,已成,手续费 0.852 元)。
> 以下为**完整原始返回**(含空/默认字段),与 2.1 字段表一一对应。
**订单 Sample/trade/orders 原始返回)**
```json
{
"m_bEnable": true,
"m_dCancelAmount": 0,
"m_dFrozenCommission": 0,
"m_dFrozenMargin": 0,
"m_dLimitPrice": 6,
"m_dOrderPriceRMB": 0,
"m_dReferenceRate": 0,
"m_dShortOccupedMargin": 0,
"m_dTradeAmount": 1200,
"m_dTradeAmountRMB": 0,
"m_dTradedPrice": 6,
"m_eCashgroupProp": 48,
"m_eCoveredFlag": 0,
"m_eEntrustType": 48,
"m_nDirection": 48,
"m_nErrorID": 2147483647,
"m_nFrontID": -1,
"m_nGroupId": 2147483647,
"m_nHedgeFlag": 49,
"m_nOffsetFlag": 49,
"m_nOpType": 2147483647,
"m_nOrderPriceType": 50,
"m_nOrderStatus": 56,
"m_nOrderStrategyType": -1645423200,
"m_nOrderSubmitStatus": 51,
"m_nRef": 0,
"m_nSessionID": -1,
"m_nStrategyID": 4322211943619263000,
"m_nTaskId": -1,
"m_nVolumeTotal": 0,
"m_nVolumeTotalOriginal": 200,
"m_nVolumeTraded": 200,
"m_strAccountID": "8882874667",
"m_strAccountKey": "2____10125____105214____49____8882874667____",
"m_strAccountName": "",
"m_strAccountRemark": "",
"m_strBrokerName": "",
"m_strCancelInfo": "",
"m_strCompactNo": "",
"m_strErrorMsg": "",
"m_strExchangeID": "SZ",
"m_strExchangeName": "深交所",
"m_strInsertDate": "20260901",
"m_strInsertTime": "101333",
"m_strInstrumentID": "001330",
"m_strInstrumentName": "博纳影业",
"m_strLocalInfo": "",
"m_strOptName": "限价卖出",
"m_strOption": "",
"m_strOrderParam": "",
"m_strOrderRef": "SZ_645009273",
"m_strOrderStrategyType": "",
"m_strOrderSysID": "645009273",
"m_strProductID": "",
"m_strProductName": "",
"m_strRemark": "",
"m_strSource": "",
"m_strUnderCode": "",
"m_strXTTrade": "其他终端",
"m_xtTag": null
}
```
**成交 Sample/trade/trades 原始返回)**
```json
{
"m_dCloseProfit": 0,
"m_dCommission": 0.852,
"m_dComssion": 0.852,
"m_dOrderPriceRMB": 0,
"m_dPrice": 6,
"m_dPriceRMB": 0,
"m_dReferenceRate": 0,
"m_dTradeAmount": 1200,
"m_dTradeAmountRMB": 0,
"m_eCoveredFlag": 48,
"m_eEntrustType": 48,
"m_eFutureTradeType": 48,
"m_nCloseTodayVolume": 0,
"m_nDirection": 48,
"m_nGroupId": 2147483647,
"m_nHedgeFlag": 49,
"m_nOffsetFlag": 49,
"m_nOrderPriceType": 50,
"m_nOrderStrategyType": -296943616,
"m_nRealOffsetFlag": -1,
"m_nRef": 0,
"m_nStrategyID": 4322211943619263000,
"m_nTaskId": -1,
"m_nVolume": 200,
"m_strAccountID": "8882874667",
"m_strAccountKey": "2____10125____105214____49____8882874667____",
"m_strAccountRemark": "",
"m_strCompactNo": "",
"m_strExchangeID": "SZ",
"m_strExchangeName": "深交所",
"m_strInstrumentID": "001330",
"m_strInstrumentName": "博纳影业",
"m_strLocalInfo": "",
"m_strOperation": "",
"m_strOptName": "限价卖出",
"m_strOrderRef": "SZ_645009273",
"m_strOrderStrategyType": "",
"m_strOrderSysID": "645009273",
"m_strProductID": "",
"m_strProductName": "",
"m_strRemark": "",
"m_strSource": "",
"m_strTradeDate": "20260901",
"m_strTradeID": "0103000030812084",
"m_strTradeTime": "101333",
"m_strXTTrade": "其他终端",
"m_xtTag": null
}
```
**语义化映射结果(供数据模型参考)**
| 语义字段 | 订单值 | 成交值 | 来源字段 |
|---|---|---|---|
| orderId | 645009273 | 645009273 | m_strOrderSysID |
| tradeId | - | 0103000030812084 | m_strTradeID |
| code | 001330 | 001330 | m_strInstrumentID |
| name | 博纳影业 | 博纳影业 | m_strInstrumentName |
| exchange | SZ | SZ | m_strExchangeID |
| direction | 卖 | 卖 | m_nOffsetFlag=49 + m_strOptName=限价卖出 |
| status | 56 已成 | 已成 | m_nOrderStatus |
| orderVolume | 200 | - | m_nVolumeTotalOriginal |
| tradedVolume | 200 | 200 | m_nVolumeTraded / m_nVolume |
| limitPrice | 6 | - | m_dLimitPrice |
| price | 6 | 6 | m_dTradedPrice / m_dPrice |
| amount | 1200 | 1200 | m_dTradeAmount |
| commission | - | 0.852 | m_dCommission |
| date | 20260901 | 20260901 | m_strInsertDate / m_strTradeDate |
| time | 101333 | 101333 | m_strInsertTime / m_strTradeTime |
| accountId | 8882874667 | 8882874667 | m_strAccountID |
### 2.3 数据时效研究(能查多少天?)
> 核实日期:2026-09-01 结论来源:QMT 桥 OpenAPI 规范 + 大 QMT 内置 API 官方文档(dict.thinktrader.net/innerApi/
**结论:/trade/orders 与 /trade/trades 只能返回当日(当天交易日)的委托与成交,无法查历史。**
**证据链**
1. **QMT 桥 OpenAPI 规范**:两个端点的 summary 与 description 均明确「**当日**委托查询」「**当日**成交查询」,且参数只有 code/status/strategy_nameorders)或无参数(trades)——**没有日期范围参数**;
2. **底层函数**:QMT 桥数据由策略线程(adjust)调用 `get_trade_detail_data(account, 'stock', 'order'/'deal')` 缓存刷新(官方 code_examples 确认),官方示例全部是 3 参数调用,**无日期参数**;
3. **官方 Q&A(数据来源本质)**`get_trade_detail_data` 从**客户端本地缓存**读取(非调用时查柜台),缓存是「当前状态」——即当日委托/成交的实时快照;官方明确「不能认为查到的状态与柜台完全一致」;
4. **行情 vs 交易差异**:历史查询能力只存在于**行情**域(down_history_data / get_market_data_ex 可查历史 K线),**交易域(order/deal)没有历史查询接口**。
**对本需求的含义**
| 影响面 | 结论 |
|---|---|
| 「交易记录」tab 展示范围 | 只能展示**当日**委托与成交(当日快照) |
| 交易复盘(目标-006) | 当日复盘可完整支撑;跨日复盘需**每日落盘存档**(Q5 关联:若要做历史复盘,必须本地持久化) |
| Q5 是否持久化 | **由「可选」升级为「必要前提」**:不做持久化则复盘能力只有当日,重启即丢 |
| 数据刷新 | 当日数据准实时(300ms 缓存 + 柜台推送 50ms~6s),轮询即可 |
**结论映射**:R-007 功能范围「不做」中「历史(非当日)交易查询」一项**确认成立**(QMT Bridge 仅提供当日缓存,无法查历史);但「不做持久化」需要重新审视——若老师要支撑跨日复盘,Q5 应改为「做持久化(store.trades.json,按日存档)」或明确接受「仅当日复盘」。
### 3. 现有架构可复用点(代码审查)
| 现有能力 | 位置 | 复用方式 |
|---|---|---|
| 统一数据源抽象 + REST 直连 | src/component/QmtBridgeRestDataSource.js | 新增 getOrders()/getTrades() 方法(参照 getAsset/getPositionsm_ 字段→语义字段映射) |
| 服务端 HTTP API 领域分发 | src/api/index.js + 领域文件 | 新增 api/trades.js(订单/成交域),注入 METHODS + HANDLERS |
| 前端 tab 框架 | src/client/index.jsGENERAL_TABS | 交易记录 tab 的 render 从 PlaceholderTab 换成真实组件 |
| 服务端缓存 + 前端轮询模式 | MarketDataHub + MarketFeedmarket-snapshot | 交易数据可复用轮询模式(Q5 待定) |
| 设置显隐开关 | settings.js tabs.tradeRecords | 已预置,无需新增 |
| 涨跌着色/高亮组件 | PriceCell.jsx | 交易方向着色可复用(Q7 待定) |
## 功能范围(定稿,2026-09-01 老师拍板)
> 老师指令(2026-09-01):「先把当日订单与成交记录接进来,数据放到交易记录这个 tab 下;我会完善 QMT Bridge 历史成交的接口,所以请添加一个日期导航,根据交易日历来做一个日期导航。」
**做(本期)**
1. **数据源接入**QmtBridgeRestDataSource 新增当日委托/当日成交查询(getOrders/getTrades,语义化映射,核心字段见 2.1/2.2);
2. **服务端 API**:新增 orders / trades 端点(支持 code/status 过滤),纳入领域分发(新 api/trades.js);
3. **交易记录 tab 落地**:占位 tabodl-trade-records)替换为真实交易视图——**单表合并**:以委托行为主行,按 m_strOrderSysID 合并成交信息(聚合展示 + 行展开查看每笔成交明细);
4. **数据刷新**:服务端中转 + 前端轮询(准实时,QMT Bridge 自身 300ms 缓存;前端 3-5s 轮询);
5. **状态展示**:委托状态中文映射(未报/已报/部成/已成/已撤/废单等)、买卖方向识别(m_nOffsetFlag + m_strOptName)、成交金额/手续费展示;
6. **时间段查询(核心,v3 定稿)**:交易记录 tab 采用**起止时间段查询**(匹配历史接口按时间范围查询的形态)——标题 + 时间段控件**同一行**;控件含三个快捷按钮(**今日 / 本周 / 本月**)+ 手动起止日期(input type=date);快捷范围基于交易日历最近交易日计算且为**完整互斥时间段**(今日=最近交易日单日、本周=所在自然周**周一~周五**(不考虑周六日)、本月=所在自然月1号~月末);**交互单向模型**:preset 意图状态记录用户点的按钮,高亮=preset 匹配,不做「范围反推按钮」(2026-09-01 老师纠正:先有操作后有范围,不是反向推断);手动改日期清除 preset;「今日」范围 3-5s 轮询,其他范围一次性查询;非今日范围展示「历史数据接口开发中」占位(老师完善 QMT Bridge 历史接口后接入,服务端已预留 start/end 透传);
7. **委托↔成交合并**:按 m_strOrderSysID 关联——委托行为主行,聚合展示成交(成交量/成交价/成交额/手续费),展开后逐笔展示成交明细(2026-09-01 老师拍板)。
**不做(本期)**
- 下单/撤单等交易操作(QMT Bridge 当前为查询接口,无下单端点;目标-007 人机合一的关键决策控制权另议);
- 历史(非当日)交易查询实现(**接口由老师完善 QMT Bridge 后接入**,本期仅做日期导航占位);
- 交易数据本地持久化(Q5 本期不做,跨日复盘待历史接口就绪后评估);
- 按策略过滤交易(strategy_name 服务端过滤依赖 QMT passorder 策略名,与插件策略体系不同源,Q10 后续迭代)。
## 技术研究结论(第一轮)
1. **API 能力确认**QMT Bridge v2.0.0 已提供 /trade/orders(支持 code/status/strategy_name 过滤)+ /trade/trades(全量当日成交),数据由策略线程 300ms 缓存刷新、HTTP 线程只读缓存,**REST 轮询即可达准实时**,无需 WS 订阅(规避迭代 04 的 WS 不稳定教训);**时效边界:仅当日数据,无法查历史(2.3 数据时效研究)**;
2. **字段语义澄清(核心,官方 innerApi 字典核实)**:大 QMT 内置 APIdict.thinktrader.net/innerApi/)中,m_nDirection 对应 EEntrustBS 枚举(48=买卖/49=查询/50=撤单),官方注明「股票买卖永远是 48」——**它是操作标记不是方向**;真实买卖方向在 m_nOffsetFlagEOffset_Flag_Type48=买入开仓/49=卖出平仓,实测 49=限价卖出 完全吻合)+ m_strOptName(买卖标记中文);**方向判定规则已确定:m_nOffsetFlag 为主 + m_strOptName 交叉验证(Q3 有结论)**;
3. **字段模型清晰**:已沉淀两张字段全表(2.1 小节),语义化映射核心字段约 15 个/表(orderId/tradeId/code/name/exchange/direction/status/volumes/prices/amount/commission/time),数据模型可直接据此设计;
4. **集成路径清晰**:现有数据源抽象 / API 领域分发 / tab 框架 / 服务端缓存模式全部可复用,新增代码为增量式(1 个 API 领域文件 + 数据源 2 方法 + 前端 1 个组件);
5. **架构约束沿用**:技术约束-001/003(统一抽象 + REST 直连)、技术约束-004(webServer 自开路由)、技术约束-010 思路(服务端中转 + 缓存,前端轮询);
6. **测试隔离**:订单/成交为只读查询端点,回归测试可直连生产 API(符合技术约束-011 只读豁免),但禁止测试写操作;
7. **日期导航可行性(2026-09-01 实测)**QMT Bridge /data/calendar/trading_dates 可用(GET,支持 start/end 过滤,返回 { market, count, dates[] }dates 为 YYYYMMDD 数组)——取近 N 日交易日列表做前后切换导航即可;当日由 trading_dates 返回的最后一个交易日确定(或客户端本地日期取整)。
## 待确认问题(Q1-Q10,待老师拍板)
| # | 问题 | AI 建议 |
|---|---|---|
| # | 问题 | 结论(2026-09-01 |
|---|---|---|
| Q1 | **展示范围**:交易记录 tab 展示「当日委托 + 当日成交」两块,还是仅成交? | ✅ **两块都展示**:委托表(含状态)+ 成交表(含费用),tab 内分区块 |
| Q2 | **数据刷新机制**:服务端中转 + 前端轮询?周期? | ✅ 服务端中转 + 前端 3-5s 轮询(复用 market-snapshot 模式);QMT 自身 300ms 缓存 |
| Q3 | **买卖方向判定**m_nDirection 恒 48 不判方向,方向取哪个字段? | ✅ **m_nOffsetFlag 为主(48买/49卖)+ m_strOptName 展示交叉验证**(官方枚举核实,Q3 闭环) |
| Q4 | **委托状态展示**:默认过滤已撤/废单?还是全量? | ✅ 全量展示 + 状态列 + 可选筛选(active/全部),默认全量(首版简化:全量展示即可) |
| Q5 | **是否持久化**:跨日复盘的前提是落盘? | ✅ **本期不做持久化**(当日实时查询);跨日复盘待历史接口就绪后评估(老师将完善 QMT Bridge 历史成交接口) |
| Q6 | **委托↔成交关联形态**:两表还是单表合并? | ✅ **单表合并**:委托行为主行 + 展开/折叠查看每笔成交(老师拍板,2026-09-01);聚合成交信息在委托行展示,展开后逐笔明细 |
| Q7 | **方向着色**:买/卖如何着色? | ✅ 遵循 A 股习惯:**红买绿卖**(与行情红涨绿跌相反语义;前端实现时用色值区分) |
| Q8 | **手续费/成交额精度**:展示位与单位? | ✅ 成交额元(2 位小数)、手续费元(3 位小数)、成交量股 |
| Q9 | **优先级**:老师定级? | ✅ **P1**(交易复盘是目标-006 核心输入,tab 已占位) |
| Q10 | **与策略联动**:按插件策略过滤交易? | ✅ 本期不做(strategy_name 是 QMT passorder 概念,与插件策略不同源;后续迭代) |
| Q11 | **日期导航 → 时间段查询**2026-09-01 演进):基于交易日历的日期导航如何做? | ✅ **起止时间段查询**(老师反馈 v3):放弃单日前后导航,改为时间段(start~end);标题 + 时间段控件同一行;快捷按钮 今日/本周/本月 + 手动起止日期;匹配历史接口按时间范围查询的形态;服务端 orders/trades 预留 start/end 透传 |
## 展示设计:单表合并(委托为主,成交并入)
> 2026-09-01 老师拍板:**不要两个表,一个表**——当日委托信息为主行,根据 m_strOrderSysID 合并成交信息。
### 合并规则
- **主行 = 一笔委托**(按 m_strOrderSysID 唯一);
- **成交聚合**:该委托的全部成交(/trade/trades 中 m_strOrderSysID 匹配)聚合进主行;
- **行展开**:点击展开/折叠,展开后逐笔展示每笔成交明细(成交时间/价格/数量/金额/手续费)。
### 委托主行列(聚合态)
| 列 | 来源 | 显示逻辑 |
|---|---|---|
| 展开箭头 | - | ▸/▾ 切换(有成交才可展开;无成交置灰/隐藏) |
| 时间 | m_strInsertTime | HH:MM:SS(委托时间) |
| 代码 | m_strInstrumentID | 6 位代码 |
| 名称 | m_strInstrumentName | 中文名 |
| 方向 | m_nOffsetFlag(48买/49卖) + m_strOptName | **红买绿卖**(Q7);显示「买入/卖出」,悬停显示完整操作名(限价卖出) |
| 状态 | m_nOrderStatus | 中文映射:未报/待报/已报/已报待撤/部成待撤/部撤/已撤/部成/已成/废单 |
| 委托量 | m_nVolumeTotalOriginal | 整数 + 股 |
| 成交量 | Σ成交 m_nVolume(优先)或 m_nVolumeTraded | 聚合成交总量;无成交显示 — |
| 委托价 | m_dLimitPrice | 2 位小数;市价单显示「市价」 |
| **成交均价** | Σ(成交价×量)/Σ量 加权 | 聚合加权均价;无成交显示 — |
| **成交额** | Σ成交 m_dTradeAmount | 聚合求和;无成交显示 — |
| **手续费** | Σ成交 m_dCommission | 聚合求和(3 位小数);无成交显示 — |
### 展开明细行(每笔成交)
| 列 | 来源 | 显示逻辑 |
|---|---|---|
| 成交时间 | m_strTradeTime | HH:MM:SS |
| 成交价 | m_dPrice | 2 位小数 |
| 成交量 | m_nVolume | 整数 + 股 |
| 成交额 | m_dTradeAmount | 2 位小数 |
| 手续费 | m_dCommission | 3 位小数 |
| 成交号 | m_strTradeID | 次要,悬停/弱显示 |
### 显示逻辑要点
1. **未成交委托**(待报/已报/已撤/废单):成交量/成交均价/成交额/手续费列显示 —,状态列已明确表达,无需其他冗余;
2. **多笔成交**:主行聚合(加权均价 + 求和),展开逐笔;聚合金额与逐笔合计一致;
3. **排序**:委托主行**默认按时间降序**(最新在前,老师拍板 2026-09-01);**支持手动调整**——点击表头列排序(时间/代码/方向/状态/委托量/成交量/金额等可排序列),再次点击切换升/降序,默认回到降序;
4. **方向着色**:红买绿卖(表头或图例标注「红=买 绿=卖」,避免与行情红涨绿跌混淆);
5. **空态**:无委托时显示「今日暂无委托记录」;
6. **数量格式**:整数(股),不强制千分位(A股习惯)。
## 技术实现方案(草案,2026-09-01)
### 程序结构(改造后)
```
src/
├── component/
│ └── QmtBridgeRestDataSource.js # 改:新增 getOrders()/getTrades()(语义化映射)
├── api/
│ └── trades.js # 新增:订单/成交域(orders / trades / trading-dates 端点)
│ └── index.js # 改:合并 TRADE_METHODS
├── client/
│ └── views/
│ ├── TradeRecordsTab.jsx # 新增:交易记录 tab(日期导航 + 单表合并视图)
│ ├── OrderRow.jsx # 新增:委托主行(聚合成交,可展开)
│ ├── TradeDetailRows.jsx # 新增:展开的成交明细行(每笔成交)
│ └── DateNav.jsx # 新增:日期导航组件(交易日历驱动)
│ └── index.js # 改:tradeRecords tab 渲染 TradeRecordsTab
```
### 服务端
1. **数据源**QmtBridgeRestDataSource 新增
- `getOrders({ code, status })` → 调 `/trade/orders`,语义化映射(orderId/status/direction/volumes/prices/amount/time/...,见 2.2 映射表);
- `getTrades()` → 调 `/trade/trades`,语义化映射(tradeId/orderId/code/price/volume/amount/commission/time);
- `getTradingDates({ start, end })` → 调 `/data/calendar/trading_dates`(支持 start/end,返回 dates[])。
2. **API 域**:新建 api/trades.js,端点:
- `orders`args: code/status)→ dataSource.getOrders
- `trades` → dataSource.getTrades
- `trading-dates`args: start/end)→ dataSource.getTradingDates
- 纳入 registerApi 的 METHODS + HANDLERS。
3. **日期解析**:服务端提供「当日交易日」解析(trading_dates 最后一个日期,或本地日期取整),前端导航初始化用。
### 客户端
1. **TradeRecordsTab**:顶部日期导航(DateNav)+ 单表合并视图(委托主行 + 可展开成交明细);
2. **DateNav**:加载交易日列表(近 N 日,如 -30~+7),前后箭头切换,当日高亮;选中日期状态提升;非当日显示「历史数据接口开发中」提示(老师完善 QMT Bridge 历史接口后启用);
3. **数据加载**useRpc 调 orders / trades;当日 3-5s 轮询;切换日期时取消轮询、加载所选日期数据(历史接口就绪后);
4. **合并逻辑(前端)**fetch 两接口 → 按 m_strOrderSysID 分组成交 → 委托主行聚合(加权均价/求和金额/求和手续费)→ 展开时逐笔渲染成交明细;
5. **委托主行列**:展开箭头/时间/代码/名称/方向/状态/委托量/成交量/委托价/成交均价/成交额/手续费;
6. **展开明细列**:成交时间/成交价/成交量/成交额/手续费/成交号;
7. **着色**:红买绿卖(Q7 已定);方向列红色=买、绿色=卖,表头图例标注。
### 构建与验证
- pnpm run build + 页面刷新(客户端 bundle)或宿主重启(服务端模块);
- 回归:现有持仓/策略/行情端点不受影响(技术约束-011 只读豁免,可直连生产 API 验证 orders/trades/trading-dates 返回)。
## 讨论记录
> 追加式记录:日期 · 轮次 · 结论。
- **2026-09-01 · 第一轮(需求登记 + 技术研究)**:
- 老师指令:添加一个新的需求,接入订单与成交数据,开始需求和技术研究。
- AI 完成:需求登记 R-007(本文件);QMT Bridge API 面探索(/trade/orders + /trade/trades 端点确认);线上实测真实数据样本(博纳影业 001330.SZ 委托+成交);现有架构代码审查(数据源/API 领域/tab 框架/缓存模式可复用);形成 Q1-Q10 待确认问题。
- 状态建议:**起草 → 讨论中**(已开始需求与技术研究),待老师确认后推进。
- **2026-09-01 · 第二轮(官方字典核实,方向字段澄清)**:
- 老师指正:QMT 桥转接的是**大 QMT 服务**,字段值应参考内置 APIinnerApi)字典:https://dict.thinktrader.net/innerApi/start_now.html?id=gdJsE0 (而非 nativeApi/xtquant)。
- AI 核实(dict.thinktrader.net/innerApi/enum_constants.html + data_structure.html):
① m_nDirection 对应 enum_EEntrustBS48=买卖/49=查询/50=撤单),官方注明「股票买卖永远是 48」——**操作标记,非方向**;
② 真实方向在 m_nOffsetFlagenum_EOffset_Flag_Type48=买入开仓/49=卖出平仓/50=强平/51=平今/52=平昨/53=强减/54=本地强平);
③ m_strOptName = 买卖标记,展示委托属性的中文(实测「限价卖出」)。
- 线上实测闭环验证:博纳影业订单 m_nDirection=48、**m_nOffsetFlag=49**、m_strOptName=限价卖出 → 49=卖出平仓 与「限价卖出」完全吻合。
- 结论:**方向判定规则确定 = m_nOffsetFlag 为主(48买/49卖)+ m_strOptName 交叉验证**;Q3 由「待确认」变为「有结论,待老师确认后固化」。
- 文档同步修正:字段表 m_nDirection 释义、实测样本说明、技术研究结论第 2 条、Q3 描述。
- **2026-09-01 · 第三轮(字段全表沉淀)**:
- 老师指示「看看 QMT 桥返回了哪些字段,列一个表」「要的要的」(确认沉淀)。
- AI 实调 /trade/orders 与 /trade/trades 提取完整原始字段,整理为两张字段全表(见 2.1 小节),标注 ★ 语义化映射核心字段(约 15 个/表)。
- 关键点:① m_nOffsetFlag+m_strOptName 双字段定方向(Q3 闭环);② m_strOrderSysID 关联委托↔成交;③ 手续费字段有拼写变体 m_dComssion(取值优先 m_dCommission);④ 大量期权/期货/人民币换算字段在股票场景为空/默认值,映射时裁剪。
- **2026-09-01 · 第四轮(Sample Data 沉淀)**
- 老师指示「今天有成交数据,帮我在文档中完善 sample data」。
- AI 实调最新 /trade/orders + /trade/trades(当日 1 笔委托 + 1 笔成交:博纳影业 001330.SZ 限价卖出 200 股 @6.00,已成,手续费 0.852 元),完整原始 JSON 沉淀为 2.2 小节 Sample Data。
- 同时给出语义化映射结果表(orderId/tradeId/code/name/exchange/direction/status/volumes/prices/amount/commission/time/accountId 共 16 字段),作为数据模型直接参考。
- 注:实调确认今日数据仅此一笔(订单 645009273 = 成交 0103000030812084 同号关联),sample 覆盖委托↔成交完整链路。
- **2026-09-01 · 第五轮(数据时效研究)**:
- 老师提问:这两个接口可以返回多少天以内的成交记录?
- AI 核实(QMT 桥 OpenAPI + 大 QMT 官方文档 code_examples/question_answer):/trade/orders 与 /trade/trades **只能返回当日数据**——规范明确「当日」且无日期参数;底层 get_trade_detail_data 从客户端本地缓存读取(当前状态快照),交易域无历史查询接口(历史能力仅限行情域)。
- 沉淀 2.3 数据时效研究小节;Q5 持久化由「可选」升级为「跨日复盘的必要前提」,待老师拍板。
- **2026-09-01 · 第六轮(范围定稿 + 日期导航)**:
- 老师拍板:① 先把**当日订单与成交记录接进来**,数据放到**交易记录 tab** 下;② 老师将完善 QMT Bridge **历史成交接口**;③ 请添加一个**日期导航**,**根据交易日历**做一个日期导航。
- AI 更新:功能范围定稿(当日委托+成交 + 日期导航占位);Q1-Q10 全部有结论(Q5 本期不做持久化);新增 Q11 日期导航结论;技术实现方案草案(数据源 3 方法 / api/trades.js / TradeRecordsTab + DateNav);实测 trading_dates 接口可用。
- 状态:R-007 从「讨论中」推进,功能范围已定稿,待老师确认定稿后进入计划/迭代。
- **2026-09-01 · 第七轮(单表合并展示设计)**:
- 老师指示讨论委托/成交的显示字段与显示逻辑;AI 提出两表方案;老师拍板:**不要两个表,要一个表**——当日委托信息为主行,根据 m_strOrderSysID 合并成交信息。
- 三个决策点老师逐条确认:① **委托行展开/折叠**——主行显示订单状态 + 聚合成交信息,展开后逐笔成交明细;② 委托未成交(已撤/废单/待报)——状态列已明确,成交列显示 —;③ 聚合展示在委托行,展开后分别展示。
- AI 沉淀「展示设计:单表合并」章节:委托主行列(时间/代码/名称/方向/状态/委托量/成交量/委托价/成交均价/成交额/手续费)+ 展开明细列(成交时间/价格/量/额/手续费/成交号)+ 合并规则(按 m_strOrderSysID 分组、加权均价、求和、未成交显示 —)。
- 技术方案同步:组件改为 TradeRecordsTab + OrderRow(委托主行)+ TradeDetailRows(展开明细)+ DateNav;前端合并逻辑明确(fetch 两接口 → 分组 → 聚合)。
- **2026-09-01 · 第九轮(日期导航改版 v2)**:
- 老师反馈:顶部日期按钮没有美感——不要做成一个个按钮、不要加边框;以当前**最后一个交易日**为基准,先是**前面 7 个日期**;做成**一行**,把前一日/后一日的导航箭头也放进同一行;每个日期用**小竖线**分隔。
- AI 重写 DateNav v2:一行式布局(◀ 日期|日期|...|日期 ▶),日期为纯文本无边框,竖线 | 分隔,选中绿色加粗;基准 = 交易日历最后一个交易日(最近交易日),显示最近 + 前 7 个共 8 个;箭头 ◀ ▶ 同在一行(上一个/下一个交易日);TradeRecordsTab 的「今日」基准同步改为最近交易日(onLastDate 通知)。
- **2026-09-01 · 第十轮(时间段查询重构 v3)**:
- 老师反馈:看了历史交易接口,是按**传入时间范围**确定查询哪些日期的记录——现有单日导航逻辑不适合;倾向**按日期查询但能指定起止时间**;同时布局浪费空间(标题一行、日期导航一行)——① 日期导航改为**起止时间段**,不再用前一日/后一日;② 时间段控件与**标题同一行**;③ 新增三个功能键:**今日 / 本周 / 上周**快速查询近期数据,其余用户手动指定日期。
- AI 重构:删除 DateNav,新增 RangeSelector(今日/本周/上周快捷 + 起止日期 input);TradeRecordsTab 标题 + 时间段同一行;今日范围 4s 轮询、其他范围一次性查询;服务端 orders/trades 预留 start/end 透传(匹配历史接口形态);历史范围占位「历史数据接口开发中」。
- 决策:今日=最近交易日单日;本周=所在自然周周一~周日(完整周);本月=所在自然月1号~月末(完整月);均基于交易日历最近交易日计算。
- 2026-09-01 修正一:快捷按钮由「今日/本周/上周」改为「**今日/本周/本月**」(老师反馈,去掉上周、新增本月)。
- 2026-09-01 修正二(老师纠正):快捷范围为**完整时间段**(本月=9/1~9/30、本周=周一~周日),不做「截至今天」截断——否则月初第一天点「本月」会与「今日」重合、无法操作;这是操作逻辑问题,不是数据问题。
- 2026-09-01 · 第十一轮(费用计算规则,老师定):m_dCommission 的值是**佣金费率(万分之)**——如 0.852 = 万分之0.852 = 0.0000852(不是金额 0.852 元)。费用规则:佣金 = 成交额 × 万0.852,不足 5 元按 5 元(账号没免五);卖出加印花税(万分之5,2023-08-28 起)和过户费(万分之0.1,双向,无最低,2022-04-29 起,老师确认费率);过户费收费方中登、ETF/LOF/可转债/REITs 免收。已实现 calcTradeFees 逐笔计算 + 前端聚合展示(总费用/佣金/印花税/过户费四列)。
- 2026-09-01 修正五(佣金合并计费,老师纠正):**同一笔委托分多笔成交,按总成交金额合并算一次佣金**(不逐笔重复收费);多笔独立委托(分开下单)才各自算佣金、各自触发 5 元最低。实现:calcOrderFees 委托层合并计费(Σ成交额×费率→最低5元一次),mapTrade 保留原始成交字段不再逐笔算费;前端聚合调用 calcOrderFees,展开明细只展示成交本身(费用在委托主行)。验证:200股拆两笔100+100,佣金5元(非10元);两笔独立委托各5元(10元)。
- 2026-09-01 修正四(本周范围,老师纠正):本周 = **周一~周五**(不考虑周六日,不是往前推 7 天/自然周周日);实现 weekSunday→weekFriday(周一+4)。
- 2026-09-01 修正三(交互模型,老师纠正):**交互是单向的**——用户先有操作(点「本月」),才有时间范围;不能反过来从「搜索范围落在哪」反向推断用户交互的是哪个按钮。实现改为 **preset 意图状态**:点按钮 → 记录 preset → 按钮高亮(单向);手动改日期 → 清除 preset(不再是任何快捷操作);轮询由 preset==='today' 决定,而非范围反推。
- **2026-09-01 · 第八轮(排序逻辑定稿)**:
- 老师拍板:委托主行排序**默认降序**(最新委托在前),**支持手动调整**(点击表头排序,可切换升/降序)。
- AI 更新:显示逻辑要点第 3 条改为「默认降序 + 表头点击排序」;可排序列包含时间/代码/方向/状态/委托量/成交量/金额等。
## 关联文档(随讨论补充)
- 计划:待 R-007 定稿后派生(符合入范围门槛)
- 迭代记录:待实现后建立
- 设计约束:待定稿后沉淀(方向判定规则、交易数据模型等)