mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 22:44:17 +08:00
- 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>
385 lines
16 KiB
Markdown
385 lines
16 KiB
Markdown
# 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 ≈ 0,u1_lo 随列表顺序单调递增(630/630 非递减)
|
||
- **板块**(880xxx):u1_hi 有 27 个不同值(0~154),分组为约 9 个一簇
|
||
- **SH 指数**(000xxx):u1_hi 范围 0~75,类似分组模式
|
||
- **创业板**(300xxx):u1_hi ≈ 0,u1_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 恒为 7654321(9/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_raw(10 只股票验证)
|
||
- unknown_5-8 恒为 0
|
||
|
||
**2026-05-27 第二轮:全市场特殊状态扫描**
|
||
- unknown_4 = 交易状态标志(停牌=32800)
|
||
- unknown_2 涨停/停牌时恒为 0
|
||
- unknown_3 与成交量正相关
|
||
|
||
**2026-05-27 第三轮:三方向并行突破**
|
||
|
||
子任务 A(MAC 协议对照):
|
||
- 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_raw(r=0.91),amount(r=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 + lo,hi=主变量(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-27:gotdx 交叉验证完全破解**
|
||
|
||
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-27:xdxr_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+code,prefix 可能是类型标识
|
||
|
||
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(结果正确但语义有误)
|