Files
easy_tdx_max/tests/unit/test_protocol_fixes.py
GitHub 574ffdd2a4 fix(client): 历史资金流当日行全零 + 主力净额列缺失(issue #52)
三个根因(全部实测核实):
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 双客户端同步修改;更新示例与三份文档;重写/新增回归
测试(当日实时逐笔路径、主力净额列断言)。
2026-08-26 15:00:53 +08:00

131 lines
4.5 KiB
Python

"""协议底层修复验证(针对 2026-04-15 审查结论)。"""
import struct
from easy_tdx.codec.price_rules import compute_price_limits
from easy_tdx.commands.security_bars import GetSecurityBarsCmd
from easy_tdx.commands.security_list import GetSecurityListCmd
from easy_tdx.commands.security_quotes import GetSecurityQuotesCmd
from easy_tdx.models.enums import KlineCategory, Market
def test_security_bars_exact_layout():
"""验证 K 线请求包布局与旧版 working bytes 完全一致。"""
cmd = GetSecurityBarsCmd(Market.SH, "600000", KlineCategory.DAY, 0, 10)
req = cmd.build_request()
# Header: 0x010C, 0x01016408, 0x1C, 0x1C
# Payload: 0x052D, 1 (Market.SH), "600000", 4 (KlineCategory.DAY), 1, 0 (start), 10, 0, 0, 0
expected = struct.pack(
"<HIHHHH6sHHHHIIH",
0x010C,
0x01016408,
0x001C,
0x001C,
0x052D,
1,
b"600000",
4,
1,
0,
10,
0,
0,
0,
)
assert req == expected
assert len(req) == 38
def test_security_list_request_length():
"""验证证券列表请求包载荷长度为 6 字节。"""
cmd = GetSecurityListCmd(Market.SH, 0)
req = cmd.build_request()
# Header 12 + Payload 6 = 18
assert len(req) == 18
payload_len = struct.unpack("<H", req[6:8])[0]
assert payload_len == 6
def test_security_quotes_limit_mapping():
"""验证涨跌停价现在返回 None,且 pre_close 正确。"""
from easy_tdx.codec.price import put_price
cmd = GetSecurityQuotesCmd([(Market.SH, "600000")])
# 构造响应报文
body = bytearray(b"\x00\x00")
body.extend(struct.pack("<H", 1))
# Record: Market(B), Code(6s), Active1(H) + ...
body.extend(struct.pack("<B6sH", 1, b"600000", 0))
body.extend(put_price(1010)) # price_raw
body.extend(put_price(-5)) # last_close_diff
body.extend(put_price(0))
body.extend(put_price(0))
body.extend(put_price(0))
body.extend(put_price(12345))
body.extend(put_price(-1010))
body.extend(put_price(100))
body.extend(put_price(10))
body.extend(struct.pack("<I", 10000))
body.extend(put_price(50))
body.extend(put_price(50))
body.extend(put_price(2))
body.extend(put_price(3))
for _ in range(20):
body.extend(put_price(0))
body.extend(struct.pack("<H", 0))
body.extend(put_price(96))
body.extend(put_price(-106))
body.extend(put_price(0))
body.extend(put_price(0))
body.extend(struct.pack("<hH", 0, 0))
quotes = cmd.parse_response(bytes(body))
q = quotes[0]
assert q.limit_up is None
assert q.limit_down is None
assert q.pre_close == 10.05
def test_security_quotes_server_time_format():
"""服务器时间应按“小时 + 百万分之一小时”统一解码。"""
from easy_tdx.commands.security_quotes import _format_server_time
assert _format_server_time(9500000) == "09:30:00.000"
assert _format_server_time(14999212) == "14:59:57.163"
def test_compute_price_limits_for_stocks():
"""普通股票 / ST / 创业板 / 科创板 / 北交所规则应可正确计算。"""
assert compute_price_limits(Market.SH, "600000", "浦发银行", 10.05) == (11.06, 9.05)
assert compute_price_limits(Market.SH, "603939", "ST益丰", 22.53) == (23.66, 21.4)
assert compute_price_limits(Market.SZ, "301269", "华大九天", 86.36) == (103.63, 69.09)
assert compute_price_limits(Market.SH, "688981", "中芯国际", 101.52) == (121.82, 81.22)
assert compute_price_limits(Market.BJ, "920002", "万达轴承", 84.36) == (109.67, 59.05)
def test_compute_price_limits_for_indices():
"""指数与板块类代码不应计算涨跌停。"""
assert compute_price_limits(Market.SH, "999999", "上证指数", 4026.63) == (None, None)
assert compute_price_limits(Market.SH, "880005", "涨跌家数", 1841.0) == (None, None)
assert compute_price_limits(Market.SZ, "399001", "深证成指", 10412.63) == (None, None)
def test_compute_price_limits_for_newly_listed_stocks():
"""上市初期限价窗口应返回 None。"""
assert compute_price_limits(Market.SH, "600001", "主板新股", 10.0, listed_days=5) == (
None,
None,
)
assert compute_price_limits(Market.SH, "600001", "主板新股", 10.0, listed_days=6) == (11.0, 9.0)
assert compute_price_limits(Market.BJ, "920002", "北交所新股", 84.36, listed_days=1) == (
None,
None,
)
assert compute_price_limits(Market.BJ, "920002", "北交所新股", 84.36, listed_days=2) == (
109.67,
59.05,
)