mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 16:54:20 +08:00
Fix transport and fund flow protocol edge cases
This commit is contained in:
@@ -4,6 +4,7 @@ import struct
|
||||
from unittest.mock import patch
|
||||
|
||||
from xmtdx import Market, TdxClient
|
||||
from xmtdx.client import _classify_fund_flow
|
||||
from xmtdx.models.bar import SecurityBar
|
||||
from xmtdx.models.quote import SecurityQuote
|
||||
from xmtdx.models.security import SecurityInfo
|
||||
@@ -18,7 +19,7 @@ def test_get_fund_flow_logic(_mock_conn_cls):
|
||||
|
||||
# 构造模拟 Tick 数据
|
||||
mock_recs = [
|
||||
TransactionRecord(10, 0, 100.0, 100, 0, 0), # super_in (100*100*100 = 100w)
|
||||
TransactionRecord(10, 0, 100.0, 101, 0, 0), # super_in (100*101*100 = 101w)
|
||||
TransactionRecord(10, 1, 10.0, 250, 1, 0), # large_out (10*250*100 = 25w)
|
||||
TransactionRecord(10, 2, 10.0, 10, 0, 0), # small_in (10*10*100 = 1w)
|
||||
]
|
||||
@@ -26,10 +27,24 @@ def test_get_fund_flow_logic(_mock_conn_cls):
|
||||
with patch.object(TdxClient, "get_transaction_data", return_value=mock_recs):
|
||||
flow = client.get_fund_flow(Market.SH, "600000")
|
||||
|
||||
assert flow.super_in == 1000000.0
|
||||
assert flow.super_in == 1010000.0
|
||||
assert flow.large_out == 250000.0
|
||||
assert flow.small_in == 10000.0
|
||||
assert flow.main_net_inflow == 1000000.0 - 250000.0
|
||||
assert flow.main_net_inflow == 1010000.0 - 250000.0
|
||||
|
||||
|
||||
def test_classify_fund_flow_exact_thresholds_use_lower_bucket():
|
||||
"""恰好命中阈值时,应落入较低一档。"""
|
||||
flow = _classify_fund_flow([
|
||||
TransactionRecord(10, 0, 100.0, 100, 0, 0), # 100w -> large
|
||||
TransactionRecord(10, 1, 100.0, 20, 0, 0), # 20w -> medium
|
||||
TransactionRecord(10, 2, 100.0, 4, 0, 0), # 4w -> small
|
||||
])
|
||||
|
||||
assert flow.super_in == 0.0
|
||||
assert flow.large_in == 1000000.0
|
||||
assert flow.medium_in == 200000.0
|
||||
assert flow.small_in == 40000.0
|
||||
|
||||
@patch("xmtdx.client.TdxConnection")
|
||||
def test_get_security_list_all_filtering(_mock_conn_cls):
|
||||
@@ -103,11 +118,11 @@ def test_get_history_fund_flow_parsing():
|
||||
body = bytearray(9)
|
||||
body.extend(struct.pack("<H", 1)) # 1 record
|
||||
|
||||
# Record: Date(I) + 8 * custom_float(i)
|
||||
# Record: Date(I) + 8 * custom_float(uint32)
|
||||
# 2025-01-08
|
||||
date = 20250108
|
||||
# 模拟 8 个流向金额
|
||||
record = struct.pack("<Iiiiiiiii", date, 100, 200, 300, 400, 500, 600, 700, 800)
|
||||
record = struct.pack("<IIIIIIIII", date, 100, 200, 300, 400, 500, 600, 700, 800)
|
||||
body.extend(record)
|
||||
|
||||
cmd = GetHistoryFundFlowCmd(Market.SH, "600000", 0, 1)
|
||||
@@ -130,7 +145,7 @@ def test_get_history_fund_flow_fallback(_mock_conn_cls):
|
||||
]
|
||||
txn_map = {
|
||||
20250108: [
|
||||
TransactionRecord(10, 0, 100.0, 100, 0, 0),
|
||||
TransactionRecord(10, 0, 100.0, 101, 0, 0),
|
||||
TransactionRecord(10, 1, 10.0, 250, 1, 0),
|
||||
],
|
||||
20250109: [
|
||||
@@ -155,7 +170,7 @@ def test_get_history_fund_flow_fallback(_mock_conn_cls):
|
||||
year=2025,
|
||||
month=1,
|
||||
day=8,
|
||||
super_in=1000000.0,
|
||||
super_in=1010000.0,
|
||||
super_out=0.0,
|
||||
large_in=0.0,
|
||||
large_out=250000.0,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""协议底层修复验证(针对 2026-04-15 审查结论)。"""
|
||||
|
||||
import struct
|
||||
from unittest.mock import patch
|
||||
|
||||
from xmtdx.codec.price_rules import compute_price_limits
|
||||
from xmtdx.commands.fund_flow import GetHistoryFundFlowCmd
|
||||
@@ -93,6 +94,14 @@ def test_security_quotes_limit_mapping():
|
||||
assert q.pre_close == 10.05
|
||||
|
||||
|
||||
def test_security_quotes_server_time_format():
|
||||
"""服务器时间应按“小时 + 百万分之一小时”统一解码。"""
|
||||
from xmtdx.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)
|
||||
@@ -123,3 +132,32 @@ def test_compute_price_limits_for_newly_listed_stocks():
|
||||
assert compute_price_limits(
|
||||
Market.BJ, "920002", "北交所新股", 84.36, listed_days=2
|
||||
) == (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("xmtdx.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])
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""同步 transport 回归测试。"""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from xmtdx.exceptions import TdxConnectionError
|
||||
from xmtdx.transport.sync import TdxConnection
|
||||
|
||||
|
||||
class _FakeSocket:
|
||||
def __init__(self) -> None:
|
||||
self.timeout: float | None = None
|
||||
self.connected_to: tuple[str, int] | None = None
|
||||
self.closed = False
|
||||
|
||||
def settimeout(self, timeout: float) -> None:
|
||||
self.timeout = timeout
|
||||
|
||||
def connect(self, address: tuple[str, int]) -> None:
|
||||
self.connected_to = address
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
def test_sync_connection_closes_socket_when_setup_fails() -> None:
|
||||
sock = _FakeSocket()
|
||||
conn = TdxConnection("127.0.0.1", port=7709, timeout=0.2)
|
||||
|
||||
with patch("xmtdx.transport.sync.socket.socket", return_value=sock), patch.object(
|
||||
TdxConnection,
|
||||
"_send_setup",
|
||||
side_effect=TdxConnectionError("setup failed"),
|
||||
):
|
||||
try:
|
||||
conn.connect()
|
||||
except TdxConnectionError as exc:
|
||||
assert "setup failed" in str(exc)
|
||||
else: # pragma: no cover - 防御性断言
|
||||
raise AssertionError("expected setup failure")
|
||||
|
||||
assert sock.timeout == 0.2
|
||||
assert sock.connected_to == ("127.0.0.1", 7709)
|
||||
assert sock.closed is True
|
||||
assert conn._sock is None
|
||||
Reference in New Issue
Block a user