Files
easy_tdx_max/src/xmtdx/commands/security_list.py
T
minionszywandClaude Sonnet 4.6 283682f6b4 feat: 初始实现 xmtdx —— 从零实现通达信 TCP A 股行情客户端
替代年久失修的 pytdx,修复已知 bug,保留未解字段供逆向分析。

主要内容:
- codec 层:get_price 变长编码、get_volume 自定义浮点、datetime/frame 解析
- transport 层:同步(socket)+ 异步(asyncio)双实现,共用命令层
- 命令层(11 条):security_count/list/quotes/bars、minute_time(今日+历史)、
  transaction(当日+历史)、xdxr_info、finance_info、company_info
- 高层 API:TdxClient + AsyncTdxClient
- 单元测试 26 条,全部通过;真实服务器集成测试覆盖全部命令

修复 pytdx Bug #1–5:xdxr 循环读取错误位置、GBK 截断崩溃、
pre_close 误用 get_volume、逐笔/分时未解字段被丢弃

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 19:55:19 +08:00

68 lines
2.3 KiB
Python
Raw 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.
"""获取证券列表命令(每页最多1000条,按 start 分页)。
修复 pytdx Bug #2GBK 解码使用 errors='replace',截断多字节序列不再崩溃。
修复 pytdx Bug #3pre_close 使用 get_price 解码,而非 get_volume。
"""
import struct
from ..codec.price import get_price
from ..models.enums import Market
from ..models.security import SecurityInfo
from .base import BaseCommand
_RECORD_SIZE = 29
class GetSecurityListCmd(BaseCommand[list[SecurityInfo]]):
"""获取指定市场从 start 开始的证券列表。"""
def __init__(self, market: Market, start: int) -> None:
self.market = market
self.start = start
def build_request(self) -> bytes:
header = bytes.fromhex("0c01186401010600060050 04".replace(" ", ""))
return header + struct.pack("<HH", int(self.market), self.start)
def parse_response(self, body: bytes) -> list[SecurityInfo]:
(num,) = struct.unpack_from("<H", body, 0)
pos = 2
results: list[SecurityInfo] = []
for _ in range(num):
raw = body[pos : pos + _RECORD_SIZE]
(
code_bytes,
volunit,
name_bytes,
_unknown1, # 4字节,含义未明
decimal_point,
pre_close_raw,
_unknown2, # 4字节,含义未明
) = struct.unpack("<6sH8s4sBI4s", raw)
code = code_bytes.decode("utf-8", errors="replace").rstrip("\x00")
# 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
results.append(
SecurityInfo(
market=self.market,
code=code,
name=name,
volunit=volunit,
decimal_point=decimal_point,
pre_close=pre_close,
_raw=raw,
)
)
pos += _RECORD_SIZE
return results