Files
easy-tdx/docs/protocol-unknown-fields.md
GitHubandClaude Opus 4.8 04c2be1d7f fix(backtest): resolve mypy and ruff lint issues
- dsl.py: use NDArray type annotations, fix None narrowing
- cli.py: add type annotations, fix import sorting
- strategy.py: fix UP038 isinstance, add noqa for I() method name
- tests: fix E712 bool comparison assertions

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 18:21:25 +08:00

385 lines
16 KiB
Markdown
Raw Permalink 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.
# TDX 协议未知字段逆向分析
本文档追踪 TDX 协议中尚未完全暴露的结构,作为持续逆向的参考。
## 一、实时行情 unknown 字段(security_quotes
`commands/security_quotes.py`,每只股票一条变长记录。
| 字段 | 编码 | 状态 | 含义 |
|------|------|------|------|
| unknown_0 | get_price | **已确认** | 服务器时间戳(小时+百万分之一小时),`_format_server_time()` 解码 |
| unknown_1 | get_price | **已确认** | **= -price_raw**(差分编码负基准值) |
| unknown_2 | get_price | **已确认** | **指数/板块**:集合竞价成交金额(`decoded * 100` = 元);**个股**`= b_vol + s_vol - vol`(舍入残差,≈0 |
| unknown_3 | get_price | **已确认** | **个股**:集合竞价成交金额(`decoded * 100` = 元);**指数/板块**:无意义(负值) |
| unknown_4 | H (uint16) | **已确认** | **交易状态标志**:停牌=32800(0x8020);非停牌值结构已知(见下文) |
| unknown_5-8 | get_price | **已确认** | 恒为 0,保留字段 |
### unknown_2 详细分析(已确认:指数集合竞价成交金额 / 个股舍入残差)
**双重含义,取决于品种类型:**
1. **指数/板块**`IndexOpenAmount` — 集合竞价成交金额。`decoded * 100` = 元。
- 上证指数(000001.SH)u2=125,967,040 → 开盘金额 ≈ 1,260 亿元
- 深证成指(399001.SZ)u2=157,987,536 → 开盘金额 ≈ 1,580 亿元
2. **个股**`= b_vol + s_vol - vol`(舍入残差)。
- 验证方法:对 20 只股票逐一计算 `s_vol + b_vol - u2`,结果与 `vol` 精确匹配(20/20
- 涨停时恒为 0(买量=总量,卖量=0);停牌时恒为 0(vol=0);正常股票范围 -25~55
来源:Go 实现 gotdx (quant1x/gotdx) 命名为 `IndexOpenAmount`,注释"指数有效-集合竞价成交金额=开盘成交金额"。
### unknown_3 详细分析(已确认:个股集合竞价成交金额)
**unknown_3 = StockOpenAmount** — 个股集合竞价(9:15-9:25)成交金额。
公式:`开盘成交金额(元) = decoded_u3 * 100`
推导:`开盘成交量(手) = u3 * 100 / (open_price * 100) = u3 / open_price`
验证数据:
| 股票 | u3 | 开盘金额(万元) | 开盘量(手) | 占总金额% |
|------|-----|--------------|-----------|----------|
| 平安银行(000001) | 73,598 | 736 | 6,840 | 0.81% |
| 京东方A(000725) | 4,825,923 | 48,259 | 832,056 | 1.66% |
| 贵州茅台(600519) | 754,092 | 7,541 | 595 | 0.71% |
| 比亚迪(002594) | 294,144 | 2,941 | 3,064 | 0.85% |
合理性:开盘占比 0.22%~1.66%,符合 A 股集合竞价的典型比例。当日固定值(不随盘中变化)。
对指数/板块:u3 为负值,无实际意义。指数的开盘金额由 unknown_2 承载。
**排除历史假说**
- 非换手率(R²=0.507
- 非简单成交额衍生(amount/u3 比值变化 9 倍)
- 与 amount 高度相关(r=+0.95)因为活跃股的开盘金额与总金额正相关
来源:Go 实现 gotdx (quant1x/gotdx) 命名为 `StockOpenAmount`,注释"个股有效-集合竞价成交金额=开盘成交金额"。
### unknown_4 非停牌值分析
**已确认的结构特征**
| 条件 | u4 值 | 说明 |
|------|-------|------|
| 停牌 | 32800 (0x8020) | bit15=1,确认为停牌标志 |
| 指数 | 2 (0x0002) | 所有指数恒为 2 |
| 个股 | 598~6166 | 日内固定值(不随交易变化) |
**字节结构**(个股非停牌值):
```
u4 = hi_byte * 256 + lo_byte
hi_byte: 0~24, 23个唯一值,是 u4 的主变量(r=0.9992
lo_byte: 仅 5 个可能值 → {2, 22, 86, 150, 214}
低6位恒为 0x16=22, bit6-7 编码 0~3 四个级别
```
lo_byte 详解:
| lo (hex) | 十进制 | bit7-6 | 含义推测 |
|----------|--------|--------|---------|
| 0x02 | 2 | - | 指数专用 |
| 0x16 | 22 | 00 | 个股级别0 |
| 0x56 | 86 | 01 | 个股级别1 |
| 0x96 | 150 | 10 | 个股级别2 |
| 0xD6 | 214 | 11 | 个股级别3 |
**与行情指标的相关性**:与所有 MAC 标准字段弱相关(|r|<0.43),最高为 active2 (r=0.42)。
hi_byte 与 active2(成交笔数) 中等相关,但不足以确认映射。
**推测**:通达信内部计算的日级分类/热度指标。lo_byte 编码股票分类属性,hi_byte 编码具体指标值。
所有已知 TDX 实现(gotdx、pytdx、injoyai/tdx)均标记为"保留/未知",无权威解释。
### 附:MAC 协议 build_bitmap() Bug
`codec/bitmap.py``build_bitmap()` 无法处理 bit 值 >= 128 的字段(如 BID_ASK_DIFF=0x8C),
会引发 OverflowError。这些字段应放入 4 字节控制区(位 128-159),但当前实现未处理这种分离。
## 二、证券列表 unknown 字段(security_list
`commands/security_list.py`,每条 29 字节记录:
```
offset 0: 6s code
offset 6: H volunit
offset 8: 8s name (GBK)
offset 16: I _unknown1 ← 已确认:排序/分组字段
offset 20: B decimal_point
offset 21: I pre_close_raw (实际是 IEEE float32,非通达信自定义浮点)
offset 25: I _unknown2 ← 已确认:私有时间戳/更新日期
```
### _unknown1(已确认:排序/分组字段)
拆分为两个 uint16 LE`u1_lo`(offset 16) + `u1_hi`(offset 18)。
- **u1_hi** = 分组 ID,同组内的板块/指数共享相同的 u1_hi 值
- **u1_lo** = 组内排序位置,与列表顺序单调递增
按品种统计:
- **SZ 主板**000xxx):u1_hi ≈ 0u1_lo 随列表顺序单调递增(630/630 非递减)
- **板块**880xxx):u1_hi 有 27 个不同值(0~154),分组为约 9 个一簇
- **SH 指数**000xxx):u1_hi 范围 0~75,类似分组模式
- **创业板**300xxx):u1_hi ≈ 0u1_lo 随列表顺序递增
不是浮点数,不是价格,不是简单计数器——是通达信内部的排序/分组标识。
### _unknown2(已确认:私有时间戳)
大部分为 0,非零情况:
- SZ 主板:631/631 全部非零
- SH 指数:20/200 非零
- 板块:20/797 非零
- 创业板:0/369
字节级分析(offset 25-28 = b25, b26, b27, b28):
- **b28 编码年份**:值 22~37 对应 2010~2025(公式:year = b28 + 1988
- b26 在 SZ 主板中恒为 0x31(49),在 SH 指数中为 0x29~0x31
- b27 范围 5~254,不是简单的月份或日序
- 不是 IEEE float32,不是通达信自定义浮点,不是标准日期格式
推测:记录了某种更新/修改日期,但具体编码方式仍有部分未解。
### 重大发现:pre_close 实际是 IEEE float32
offset 21 的 pre_close_raw 一直被当作通达信自定义浮点编码(`_decode_volume`)。
实际上它是 **标准 IEEE float32**。但 `_decode_volume()` 对价格范围(0.01~99999.99)的
IEEE float32 位模式产生数学上相同的结果——纯属巧合,当前代码正确工作但原因理解有误。
pytdx 同样有此问题(也用 `get_volume` 解码此字段)。
## 三、其他待破解字段
### 3.1 响应帧头 16 字节(已确认)
`codec/frame.py`:前 12 字节通过 gotdx 交叉验证已完全破解。
**字节级结构**gotdx `StdResponseHeader`):
| 偏移 | 大小 | 名称 | 含义 |
|------|------|------|------|
| 0-3 | uint32 | magic | 协议魔数,恒为 7654321 (0x0074CBB1) |
| 4 | uint8 | ZipFlag | bit4=1 表示 body 已压缩 (0x1C=压缩, 0x0C=未压缩) |
| 5-8 | uint32 | SeqID | 请求 bytes 1-4 的回显(命令标识匹配) |
| 9 | uint8 | 保留 | 观察到恒为 0x00 |
| 10-11 | uint16 | Method | 请求 bytes 10-11 的回显 |
| 12-13 | uint16 | ZipSize | body 压缩后字节数 |
| 14-15 | uint16 | UnZipSize | body 解压后字节数 |
**验证数据**3 次连接,setup + data 命令):
- magic 恒为 76543219/9 响应验证)
- ZipFlag bit4 与 zipsize≠unzipsize 完全一致(4/4 响应验证)
- SeqID 与请求 bytes 1-4 精确匹配(4/4 响应验证)
- Method 与请求 bytes 10-11 精确匹配(4/4 响应验证)
**兼容说明**:代码使用 `struct "<IIIHH"` 解码,正确提取 ZipSize 和 UnZipSize。FrameHeader 字段已更名为 `magic/seq_id/method`
来源:gotdx (quant1x/gotdx) `quotes/base_message.go` `StdResponseHeader` 结构体。
### 3.2 分时数据 unknown_1 字段
`commands/minute_time.py:57``unknown_1`pytdx 称 `reversed1`)。
**状态:未确定**。所有已知实现(pytdx、gotdx、injoyai/tdx、xmtdx、easy-tdx)均丢弃此字段。
**排除的假设**(历史分时数据 600000.SH 20250108 验证):
| 假设 | 相关系数 r | 说明 |
|------|-----------|------|
| VWAP (cum_amt/cum_vol) | 0.006 | 完全不匹配 |
| 累计成交额 | -0.029 | 完全不匹配 |
| 每分钟成交额 | -0.200 | 弱相关 |
| delta_vwap | 0.361 | 最高但仍弱 |
| 成交量 vol | -0.202 | 弱相关 |
**多股票交叉验证**(历史分时 20250108):
| 股票 | 价格范围 | u1 范围 | u1 非零占比 |
|------|---------|---------|-----------|
| 000001 平安银行 | 11.40~11.62 | -27~138 | 201/240 |
| 000725 京东方A | 4.24~4.36 | -56~173 | 165/240 |
| 600519 贵州茅台 | 1426.80~1451.70 | -21105~39611 | 239/240 |
| 600000 浦发银行 | 10.22~10.36 | -238~65 | 185/240 |
u1 范围与价格非线性质相关(茅台价格 140x → u1 范围 300x),不与任何标准指标匹配。
**推测**:服务器内部计算的指标(可能与成交笔数、买卖力道、或内部排序有关),所有公开的逆向实现均无法确认其含义。
### 3.3 逐笔成交尾部字段
`commands/transaction.py:73,97``unknown_last`pytdx 原来直接丢弃。
## 四、硬跳过的字节块
### 4.1 xdxr_info 9字节响应头部(已确认:请求回显)
**位置**`commands/xdxr_info.py:33`
**字节结构**fixture `tests/fixtures/xdxr_info.hex` 验证):
| 偏移 | 大小 | 名称 | 含义 |
|------|------|------|------|
| 0-1 | uint16 | prefix | 恒为 1(可能是响应类型标识) |
| 2 | uint8 | market | 请求的 market 回显(1=SH, 0=SZ |
| 3-8 | 6s | code | 请求的股票代码回显(如 "600000" |
**验证数据**fixture 600000.SH → `01 00 01 36 30 30 30 30 30`prefix=1, market=1(SH), code="600000"。
gotdx 同样命名为 `Unknown`pytdx 直接 `pos += 9`
### 4.2 xdxr_info 每条记录1字节 padding(已确认:保留/对齐字节)
**位置**`commands/xdxr_info.py:46`
fixture 中 87 条记录全部为 0x00(87/87)。无其他可能值样本,推测为保留/对齐字节。
gotdx 命名为 `Unknown`pytdx 注释 `# noused`
### 4.3 fund_flow 9字节响应头部(已确认:同 xdxr_info 格式)
**位置**`commands/fund_flow.py:45`
与 xdxr_info 相同的 9 字节头部格式:prefix(2) + market(1) + code(6)。
后接 uint16 num(记录数量),然后是 36 字节/条的固定记录。
gotdx 未实现此命令。
### 4.4 板块文件 384 字节头部(未确认)
**位置**`codec/block.py:23`
gotdx 命名为 `Unknown [384]byte`,注释"头信息, 忽略"。所有已知实现均直接跳过。
二进制文件头部,不随网络响应变化,逆向优先级低。
### 4.5 板块记录 2813 字节(已确认:前13字节有效 + 2800字节股票列表)
**位置**`codec/block.py:39-67`
结构:`name(9s) + stock_count(H) + block_type(H) + stock_list(400×7s)`
13 字节元数据 + 2800 字节股票代码列表 = 2813 字节。gotdx 结构完全一致。
### 4.6 扩展行情 skip 字节(未确认)
| 位置 | 跳过字节数 | 说明 |
|------|-----------|------|
| ex/get_instrument_quote.py:26 | 4 字节 | market+code 后的 4 字节 unknown |
| ex/get_instrument_quote_list.py:102 | 290-140=150 字节 | 期货/港股记录的剩余字段 |
扩展行情(端口 7727)协议字段较少公开实现参考,跳过字段含义暂不明确。
## 五、财务数据 unknown 字段
`codec/financial.py``.dat` 头部 20 字节中有 10 字节未知。
每只股票的 float32 数组缺少完整的字段名映射。
## 六、完全未实现的命令
- Level-2 行情(十档买卖、逐笔委托)
- 集合竞价数据
- 融资融券明细
- 大宗交易
- 龙虎榜原始数据
- 期权/Greeks 数据
- 新三板/北交所完整支持
## 七、逆向进展日志
### 7.1 实时行情 unknown 字段
**2026-05-27 第一轮:基础采样**
- unknown_1 = -price_raw10 只股票验证)
- unknown_5-8 恒为 0
**2026-05-27 第二轮:全市场特殊状态扫描**
- unknown_4 = 交易状态标志(停牌=32800)
- unknown_2 涨停/停牌时恒为 0
- unknown_3 与成交量正相关
**2026-05-27 第三轮:三方向并行突破**
子任务 AMAC 协议对照):
- unknown_3 与 amount 相关系数 r=+0.974
- unknown_2/unknown_4 与所有 MAC 标准字段弱相关(|r|<0.36
- 发现 `build_bitmap()` 对 bit>=128 字段的 bug
子任务 B(流通股本交叉验证):
- **unknown_2 = b_vol + s_vol - vol**20/20 精确匹配)
- unknown_3 不是换手率(R²=0.507
- unknown_3 与成交额 R²=0.89,但不成固定比例
**2026-05-27 第四轮:穷举+交叉实现突破**
穷举候选分析(95 只股票):
- 最高相关候选:cur_vol*price_rawr=0.91),amountr=0.89
- 非线性变换排除幂次关系(线性最优 r=0.95)
跨实现发现(gotdx/quant1x Go TDX 库):
- **unknown_2 = IndexOpenAmount**(指数集合竞价成交金额),个股时承载舍入残差
- **unknown_3 = StockOpenAmount**(个股集合竞价成交金额),公式 `decoded * 100` = 元
- 验证:平安银行开盘 736 万元/6,840 手(占总量 0.81%),贵州茅台开盘 7,541 万元/595 手(0.71%
- 指数开盘金额:上证 1,260 亿、深证 1,580 亿,均合理
**2026-05-27 第四轮(续):unknown_4 非停牌值结构分析**
跨实现调查(gotdx、pytdx、injoyai/tdx):
- 所有实现均命名为 ReversedBytes4,注释"保留/未知"
- gotdx 用 `Open==0` 判断停牌,不依赖 u4
结构发现(96 只活跃股票):
- u4 = hi*256 + lohi=主变量(r=0.9992),范围 0~24
- lo 仅 5 值:{2(指数), 22(0x16), 86(0x56), 150(0x96), 214(0xD6)},低6位恒 0x16
- u4 在日内固定不变
- 与所有标准指标弱相关(最高 active2 r=0.42
- **结论**:通达信内部日级分类指标,具体语义不明
### 7.2 证券列表 unknown 字段
**2026-05-27 第一轮:SH 市场基础统计**
- unknown1 99.9% 非零,自定义浮点解码为 0
- unknown2 96% 为 0
**2026-05-27 第三轮:全品种深度分析**
子任务 C(全品种分类分析):
- **unknown1 = 排序/分组字段**u1_hi=分组ID, u1_lo=组内排序位置
- **unknown2 = 私有时间戳**b28 编码年份(year-1988),完整编码仍有部分未解
- **重大发现:pre_close_raw 是 IEEE float32**`_decode_volume` 恰好产生相同结果)
### 7.3 响应帧头 16 字节
**2026-05-27gotdx 交叉验证完全破解**
gotdx `StdResponseHeader` 字节级结构对照:
- magic(4B)=7654321, ZipFlag(1B)=bit4压缩标志, SeqID(4B)=请求回显
- 保留(1B)=0x00, Method(2B)=请求回显, ZipSize(2B), UnZipSize(2B)
- 9/9 响应验证通过(magic恒定、SeqID/Method精确匹配请求)
代码更新:`codec/frame.py` 字段重命名为 `magic/seq_id/method`,docstring 更新完整字节结构。
### 7.4 分时数据 unknown_1
**2026-05-27:穷举分析后确认为"未确定"**
假设穷举(多股票交叉验证):
- VWAP: r=0.006, 累计成交额: r=-0.029, delta_vwap: r=0.361
- u1 范围与价格非线性质相关(茅台价格140x → u1范围300x
- 所有已知实现(pytdx、gotdx、injoyai/tdx、xmtdx)均丢弃此字段
- xmtdx 确认为 easy-tdx fork,注释"疑似均价"但无验证
- **结论**:服务器内部指标,所有公开逆向实现均无法确认含义
### 7.5 跳过字节块
**2026-05-27xdxr_info / fund_flow / block 分析**
xdxr_info 9字节头部(fixture 验证):
- `01 00 01 36 30 30 30 30 30` = prefix(2B=1) + market(1B=SH) + code(6B="600000")
- 响应回显请求的 market+codeprefix 可能是类型标识
xdxr_info 每条1字节 padding
- 87 条记录全部为 0x00,保留/对齐字节
fund_flow 9字节头部:同 xdxr_info 格式(prefix + market + code
block 384字节头部:gotdx 命名"头信息, 忽略",所有实现跳过
### 已知 Bug 清单
1. `codec/bitmap.py build_bitmap()` — bit>=128 字段溢出(不影响现有功能)
2. `commands/security_list.py` — pre_close 用 `_decode_volume` 而非 IEEE float32(结果正确但语义有误)