# 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 "=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(结果正确但语义有误)