mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 14:34:18 +08:00
替代年久失修的 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>
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
"""获取证券列表命令(每页最多1000条,按 start 分页)。
|
||
|
||
修复 pytdx Bug #2:GBK 解码使用 errors='replace',截断多字节序列不再崩溃。
|
||
修复 pytdx Bug #3:pre_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
|