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 双客户端同步修改;更新示例与三份文档;重写/新增回归
测试(当日实时逐笔路径、主力净额列断言)。
This commit is contained in:
GitHub
2026-08-26 15:00:53 +08:00
parent 7c9e19de93
commit 574ffdd2a4
8 changed files with 158 additions and 227 deletions
-58
View File
@@ -1,10 +1,8 @@
"""协议底层修复验证(针对 2026-04-15 审查结论)。"""
import struct
from unittest.mock import patch
from easy_tdx.codec.price_rules import compute_price_limits
from easy_tdx.commands.fund_flow import GetHistoryFundFlowCmd
from easy_tdx.commands.security_bars import GetSecurityBarsCmd
from easy_tdx.commands.security_list import GetSecurityListCmd
from easy_tdx.commands.security_quotes import GetSecurityQuotesCmd
@@ -38,33 +36,6 @@ def test_security_bars_exact_layout():
assert len(req) == 38
def test_history_fund_flow_exact_layout():
"""验证历史资金流请求包布局与 K 线一致,只差 category=22。"""
cmd = GetHistoryFundFlowCmd(Market.SH, "600000", 0, 10)
req = cmd.build_request()
# Header: 0x010C, 0x01016408, 0x1C, 0x1C
# Payload: 0x052D, 1 (Market.SH), "600000", 22, 1, 0, 10, 0, 0, 0
expected = struct.pack(
"<HIHHHH6sHHHHIIH",
0x010C,
0x01016408,
0x001C,
0x001C,
0x052D,
1,
b"600000",
22,
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)
@@ -157,32 +128,3 @@ def test_compute_price_limits_for_newly_listed_stocks():
109.67,
59.05,
)
def test_history_fund_flow_uses_uint32_volume_words():
"""历史资金流金额字段必须按 uint32 传给 _decode_volume。"""
raw_words = [
0x80000001,
0xFFFFFFFF,
0x7FFFFFFF,
0x90000000,
0xA0000000,
0xB0000000,
0xC0000000,
0xD0000000,
]
body = bytearray(9)
body.extend(struct.pack("<H", 1))
body.extend(struct.pack("<IIIIIIIII", 20250108, *raw_words))
seen: list[int] = []
def fake_decode(raw: int) -> float:
seen.append(raw)
return float(raw)
with patch("easy_tdx.commands.fund_flow._decode_volume", side_effect=fake_decode):
records = GetHistoryFundFlowCmd(Market.SH, "600000", 0, 1).parse_response(bytes(body))
assert seen == raw_words
assert records[0].small_out == float(raw_words[-1])