feat: 补全 A 股历史资金流向序列功能

1. 新增命令:实现 GetHistoryFundFlowCmd (0x052d Category 22) 用于拉取历史资金分布。
2. 新增模型:增加 HistoricalFundFlow 结构,支持超大/大/中/小单的双向统计。
3. 客户端 API:TdxClient/AsyncTdxClient 增加 get_history_fund_flow() 接口。
4. 单元测试:在 test_a_share_extensions.py 中增加响应包解析逻辑验证。
5. 文档更新:README.md 同步 API 及数据模型定义。
This commit is contained in:
M
2026-04-15 15:26:51 +08:00
parent 4dabbf6d15
commit daaba7dc13
5 changed files with 158 additions and 17 deletions
+28 -16
View File
@@ -1,11 +1,13 @@
"""针对本轮 A 股增强功能的单元测试。"""
import pytest
import struct
from unittest.mock import patch, MagicMock, AsyncMock
from xmtdx import TdxClient, Market
from xmtdx.models.security import SecurityInfo
from xmtdx.models.timeseries import TransactionRecord
from xmtdx.models.quote import SecurityQuote
from xmtdx.models.stats import FundFlow, HistoricalFundFlow, MarketStat
@patch("xmtdx.client.TdxConnection")
def test_get_fund_flow_logic(mock_conn_cls):
@@ -14,14 +16,10 @@ def test_get_fund_flow_logic(mock_conn_cls):
client = TdxClient("127.0.0.1")
# 构造模拟 Tick 数据
# A股 1手=100股。
# 1. 超大单: 100元 * 100手 * 100 = 100万 (Buy)
# 2. 大单: 10元 * 250手 * 100 = 25万 (Sell)
# 3. 小单: 10元 * 10手 * 100 = 1万 (Buy)
mock_recs = [
TransactionRecord(10, 0, 100.0, 100, 0, 0), # super_in
TransactionRecord(10, 1, 10.0, 250, 1, 0), # large_out
TransactionRecord(10, 2, 10.0, 10, 0, 0), # small_in
TransactionRecord(10, 0, 100.0, 100, 0, 0), # super_in (100*100*100 = 100w)
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)
]
with patch.object(TdxClient, "get_transaction_data", return_value=mock_recs):
@@ -31,7 +29,6 @@ def test_get_fund_flow_logic(mock_conn_cls):
assert flow.large_out == 250000.0
assert flow.small_in == 10000.0
assert flow.main_net_inflow == 1000000.0 - 250000.0
assert flow.total_net_inflow == (1000000.0 + 10000.0) - 250000.0
@patch("xmtdx.client.TdxConnection")
def test_get_security_list_all_filtering(mock_conn_cls):
@@ -60,25 +57,20 @@ def test_get_security_list_all_filtering(mock_conn_cls):
all_stocks = client.get_security_list_all()
# 应该只保留 3 只 A 股 (600000, 000001, 830000)
assert len(all_stocks) == 3
codes = [s.code for s in all_stocks]
assert "600000" in codes
assert "000001" in codes
assert "830000" in codes
assert "999999" not in codes
# 检查行业挂载
s0 = next(s for s in all_stocks if s.code == "600000")
assert s0.industry_tdx == "T01"
assert s0.industry_sw == "X01"
@patch("xmtdx.client.TdxConnection")
def test_get_market_stat_mapping(mock_conn_cls):
"""测试市场统计字段映射。"""
client = TdxClient("127.0.0.1")
# 模拟 880005 行情返回
mock_quote = SecurityQuote(
Market.SH, "880005",
price=3000.0, # up
@@ -95,7 +87,27 @@ def test_get_market_stat_mapping(mock_conn_cls):
with patch.object(TdxClient, "get_security_quotes", return_value=[mock_quote]):
stat = client.get_market_stat()
assert stat.up_count == 3000
assert stat.down_count == 2000
assert stat.neutral_count == 500
assert stat.total_count == 5500
assert stat.total_amount == 50000000.0
def test_get_history_fund_flow_parsing():
"""测试历史资金流序列解析逻辑。"""
from xmtdx.commands.fund_flow import GetHistoryFundFlowCmd
# 模拟 Category 22 响应 (Header 9 + Count 2 + Body 36)
body = bytearray(9)
body.extend(struct.pack("<H", 1)) # 1 record
# Record: Date(I) + 8 * custom_float(i)
# 2025-01-08
date = 20250108
# 模拟 8 个流向金额
record = struct.pack("<Iiiiiiiii", date, 100, 200, 300, 400, 500, 600, 700, 800)
body.extend(record)
cmd = GetHistoryFundFlowCmd(Market.SH, "600000", 0, 1)
res = cmd.parse_response(bytes(body))
assert len(res) == 1
assert res[0].year == 2025
assert res[0].month == 1
assert res[0].day == 8