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
+14 -1
View File
@@ -8,6 +8,7 @@ from .commands.base import BaseCommand
from .commands.block_info import GetBlockInfoCmd, GetBlockInfoMetaCmd
from .commands.company_info import GetCompanyInfoCategoryCmd, GetCompanyInfoContentCmd
from .commands.finance_info import GetFinanceInfoCmd
from .commands.fund_flow import GetHistoryFundFlowCmd
from .commands.report_file import GetReportFileCmd
from .commands.minute_time import GetHistoryMinuteTimeDataCmd, GetMinuteTimeDataCmd
from .commands.security_bars import GetIndexBarsCmd, GetSecurityBarsCmd
@@ -24,7 +25,7 @@ from .models.enums import KlineCategory, Market
from .models.finance import CompanyInfoCategory, FinanceInfo, TdxBlock, XdxrRecord
from .models.quote import SecurityQuote
from .models.security import SecurityInfo
from .models.stats import FundFlow, MarketStat
from .models.stats import FundFlow, HistoricalFundFlow, MarketStat
from .models.timeseries import MinuteBar, TransactionRecord
from .transport.async_ import AsyncTdxConnection
from .transport.sync import KNOWN_HOSTS, TdxConnection, ping_all
@@ -362,6 +363,12 @@ class TdxClient:
return FundFlow(**stats)
def get_history_fund_flow(
self, market: Market, code: str, start: int, count: int
) -> list[HistoricalFundFlow]:
"""获取个股历史日线资金流向序列(Category 22)。"""
return self._execute(GetHistoryFundFlowCmd(market, code, start, count))
# ============================================================
# 异步客户端
@@ -664,3 +671,9 @@ class AsyncTdxClient:
stats[f"small_{direction}"] += amount
return FundFlow(**stats)
async def get_history_fund_flow(
self, market: Market, code: str, start: int, count: int
) -> list[HistoricalFundFlow]:
"""获取个股历史日线资金流向序列。"""
return await self._execute(GetHistoryFundFlowCmd(market, code, start, count))
+83
View File
@@ -0,0 +1,83 @@
"""历史资金流向命令 (Category 22)。"""
import struct
from .._binary import slice_bytes, unpack_from
from ..codec.volume import _decode_volume
from ..exceptions import TdxDecodeError
from ..models.enums import Market
from ..models.stats import HistoricalFundFlow
from .base import BaseCommand
class GetHistoryFundFlowCmd(BaseCommand[list[HistoricalFundFlow]]):
"""获取历史日线资金流向序列。"""
def __init__(self, market: Market, code: str, start: int, count: int) -> None:
self.market = market
self.code = code.encode("utf-8")
self.start = start
self.count = count
def build_request(self) -> bytes:
# 使用 0x052d 指令(K 线类指令)
# 负载长度固定为 28 字节 (0x1c)
payload_len = 0x1c
header = struct.pack(
"<HIHHH",
0x010c,
0x01016408, # 注意此处标志位与普通行情略有不同
payload_len,
payload_len,
0x052d,
)
# 参数包:Market(B), Code(6s), Category(H=22), Unknown(H=1), Start(I), Count(I), 3个Unknown(H)
params = struct.pack(
"<B6sHHIIHHH",
int(self.market),
self.code,
22, # Category 22
1, # Unknown
self.start,
self.count,
0, 0, 0
)
return header + params
def parse_response(self, body: bytes) -> list[HistoricalFundFlow]:
# 响应格式:9字节头 + 2字节数量 + 每条记录 36 字节
if len(body) < 11:
return []
(num,) = struct.unpack("<H", body[9:11])
pos = 11
results = []
for _ in range(num):
if len(body) < pos + 36:
break
# 记录格式:4字节日期 + 8个4字节自定义浮点金额
# [0]日期, [1..4]流入(超/大/中/小), [5..8]流出(超/大/中/小)
# 这里的 i 是 uint32 原始字节,随后需用 _decode_volume 解码
raw_data = struct.unpack("<Iiiiiiiii", body[pos:pos+36])
raw_date = raw_data[0]
year = raw_date // 10000
month = (raw_date // 100) % 100
day = raw_date % 100
results.append(HistoricalFundFlow(
year=year, month=month, day=day,
super_in=_decode_volume(raw_data[1]),
large_in=_decode_volume(raw_data[2]),
medium_in=_decode_volume(raw_data[3]),
small_in=_decode_volume(raw_data[4]),
super_out=_decode_volume(raw_data[5]),
large_out=_decode_volume(raw_data[6]),
medium_out=_decode_volume(raw_data[7]),
small_out=_decode_volume(raw_data[8]),
))
pos += 36
return results
+24
View File
@@ -38,3 +38,27 @@ class FundFlow:
"""全单净流入。"""
return (self.super_in + self.large_in + self.medium_in + self.small_in) - \
(self.super_out + self.large_out + self.medium_out + self.small_out)
@dataclass
class HistoricalFundFlow:
"""历史日线资金流向条目。"""
year: int
month: int
day: int
# 金额项 (单位:元)
super_in: float
super_out: float
large_in: float
large_out: float
medium_in: float
medium_out: float
small_in: float
small_out: float
@property
def main_net_inflow(self) -> float:
"""当日主力净流入。"""
return (self.super_in + self.large_in) - (self.super_out + self.large_out)