Fix security list pre-close decoding

This commit is contained in:
minionszyw
2026-04-22 18:50:07 +08:00
parent 41cb3bfd28
commit 91f6a884d5
5 changed files with 35 additions and 11 deletions
+1 -1
View File
@@ -226,7 +226,7 @@ main_net_inflow
|---|------|------|------|
| 1 | `xdxr_info` | 循环内始终读 `body[:7]`,所有记录字段相同 | 改为从当前 `pos` 读取,pos 正确推进 |
| 2 | `security_list` | GBK 解码截断时 crash | `decode('gbk', errors='replace')` |
| 3 | `security_list` | `pre_close` 错用成交量解码函数 | 改用价格解码 |
| 3 | `security_list` | `pre_close` 误当作整数价格 `/100` | 恢复为通达信自定义浮点解码 |
| 4 | `transaction` | 最后一个字段被 `_` 丢弃 | 保留为 `unknown_last` |
| 5 | `minute_time` | `reversed1` 字段被丢弃 | 保留为 `unknown_1` |
| 6 | `xdxr_info` | 股本字段用 `float(uint32)` 直解,差约 374 倍 | 改用 `_decode_volume`(通达信自定义浮点),单位万股,与 `FinanceInfo` 完全吻合 |
+4 -5
View File
@@ -1,12 +1,13 @@
"""获取证券列表命令(每页最多1000条,按 start 分页)。
修复 pytdx Bug #2GBK 解码使用 errors='replace',截断多字节序列不再崩溃。
修复 pytdx Bug #3pre_close 使用 get_price 解码,而非 get_volume
修复 pytdx Bug #3pre_close 保持使用通达信自定义浮点解码
"""
import struct
from .._binary import slice_bytes, unpack_from
from ..codec.volume import _decode_volume
from ..models.enums import Market
from ..models.security import SecurityInfo
from .base import BaseCommand
@@ -48,10 +49,8 @@ class GetSecurityListCmd(BaseCommand[list[SecurityInfo]]):
# Bug #2 修复:errors='replace' 避免截断 GBK 多字节序列时崩溃
name = name_bytes.decode("gbk", errors="replace").rstrip("\x00")
# Bug #3 修复:pre_close 不用 get_volume(成交量解码),
# 而是直接将 uint32 当作价格整数(/ 100
# 实际服务器返回的 pre_close_raw 是 price * 100 的整数
pre_close = pre_close_raw / 100.0
# pre_close_raw 与协议里的成交量/股本字段一样,使用通达信自定义浮点编码。
pre_close = _decode_volume(pre_close_raw)
results.append(
SecurityInfo(
+1 -1
View File
@@ -14,7 +14,7 @@ class SecurityInfo:
name: str # 股票名称(GBK 解码,截断字节用 replacement char 替代)
volunit: int # 成交量单位(手 = volunit 股)
decimal_point: int # 价格小数位数
pre_close: float # 昨收价(已修复 pytdx Bug #3:改用正确价格解码)
pre_close: float # 昨收价(通达信自定义浮点解码)
# 扩展字段(通过 get_security_list_all 关联 tdxhy.cfg 获得)
industry_tdx: str = "" # 通达信行业代码 (如 T1001)
+2 -2
View File
@@ -3,6 +3,6 @@
"first": {
"code": "999999",
"name": "上证指数",
"pre_close": 11654847.33
"pre_close": 3966.171142578125
}
}
}
+27 -2
View File
@@ -7,6 +7,7 @@ fixtures/ 目录下每个 .hex 文件是一次真实服务器响应的 body(
from __future__ import annotations
import pathlib
import struct
FIXTURES = pathlib.Path(__file__).parent.parent / "fixtures"
@@ -50,13 +51,37 @@ def test_security_list_parse():
r0 = records[0]
assert r0.code == "999999"
assert r0.name == "上证指数"
# pre_close is a float; the index level is stored ×100 and has many decimal places
assert abs(r0.pre_close - 11654847.33) < 1.0
assert abs(r0.pre_close - 3966.171142578125) < 0.01
# _raw present and non-empty for every record
assert all(len(r._raw) > 0 for r in records)
def test_security_list_pre_close_uses_tdx_float_for_a_share():
from xmtdx.commands.security_list import GetSecurityListCmd
from xmtdx.models.enums import Market
body = (
struct.pack("<H", 1)
+ struct.pack(
"<6sH8s4sBI4s",
b"600000",
100,
"\u6d66\u53d1\u94f6\u884c".encode("gbk"),
b"\x00\x00\x00\x00",
2,
0x411B851F,
b"\x00\x00\x00\x00",
)
)
record = GetSecurityListCmd(Market.SH, 24000).parse_response(body)[0]
assert record.code == "600000"
assert record.name == "浦发银行"
assert abs(record.pre_close - 9.72) < 0.01
def test_security_list_gbk_no_crash():
"""Bug #2 修复验证:GBK 解码不崩溃,所有记录均有 code。"""
from xmtdx.commands.security_list import GetSecurityListCmd