三个根因(全部实测核实): 1. Category 22 直连接口为虚构协议——52 台已知服务器中 46 台可达的 全部仅回 2 字节空包,从未成功过;移除死代码与臆造解析格式。 2. 历史逐笔接口当日数据要收盘清算后才有,日 K 盘中已含当日 bar, 故 start=0 的最新一行恒为全 0——当日 bar 改走当日实时逐笔接口。 3. main_net_inflow 此前仅为 dataclass property,asdict 静默丢弃, 返回 DataFrame 无主力净额列——新增 _fund_flow_df_with_net 物化 (history 紧随 date 列、当日快照放首列)。 sync + async 双客户端同步修改;更新示例与三份文档;重写/新增回归 测试(当日实时逐笔路径、主力净额列断言)。
16 KiB
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 详细分析(已确认:指数集合竞价成交金额 / 个股舍入残差)
双重含义,取决于品种类型:
-
指数/板块:
IndexOpenAmount— 集合竞价成交金额。decoded * 100= 元。- 上证指数(000001.SH):u2=125,967,040 → 开盘金额 ≈ 1,260 亿元
- 深证成指(399001.SZ):u2=157,987,536 → 开盘金额 ≈ 1,580 亿元
-
个股:
= b_vol + s_vol - vol(舍入残差)。- 验证方法:对 20 只股票逐一计算
s_vol + b_vol - u2,结果与vol精确匹配(20/20) - 涨停时恒为 0(买量=总量,卖量=0);停牌时恒为 0(vol=0);正常股票范围 -25~55
- 验证方法:对 20 只股票逐一计算
来源: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 对应 20102025(公式: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(Category 22)——已证伪并移除(Issue #52)
位置:commands/fund_flow.py(已删除)
曾有实现假设 0x052D + category=22 为"历史资金流向"指令,并按
"9 字节头 + uint16 num + 36 字节/条"解析。2026-08-26 实测(Issue #52):
全部 46 台可达标准行情服务器对该请求仅回 2 字节 body(0000 或 2003),
即 0 条记录 / ret_count 撒谎空包,从未在任何环境返回过有效数据。
响应格式属臆造,该命令已删除;资金流改为"日K取日期 + 逐笔成交重算"。
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 格式 2026-08-26 证伪(见 4.3,命令已删除)
block 384字节头部:gotdx 命名"头信息, 忽略",所有实现跳过
已知 Bug 清单
codec/bitmap.py build_bitmap()— bit>=128 字段溢出(不影响现有功能)commands/security_list.py— pre_close 用_decode_volume而非 IEEE float32(结果正确但语义有误)