From ba032da9eda7b4c9f93d273e982be10bcf28269f Mon Sep 17 00:00:00 2001 From: GitHub Date: Tue, 26 May 2026 17:35:00 +0800 Subject: [PATCH] feat: add get_board_summary() for board capital flow aggregation New MacClient/AsyncMacClient method that aggregates board member quotes into total amount, main force net inflow (1d/3d/5d), and up/down counts. Includes example demo. Co-Authored-By: Claude Opus 4.7 --- examples/15_mac_board/board_summary.py | 57 ++++++++++ pyproject.toml | 2 +- src/easy_tdx/mac/client.py | 140 ++++++++++++++++++++++++- 3 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 examples/15_mac_board/board_summary.py diff --git a/examples/15_mac_board/board_summary.py b/examples/15_mac_board/board_summary.py new file mode 100644 index 0000000..2e67736 --- /dev/null +++ b/examples/15_mac_board/board_summary.py @@ -0,0 +1,57 @@ +"""演示:板块汇总(总成交金额、主力资金流向)。 + +通过 MacClient 的 get_board_summary() 获取板块聚合数据,包含成交额、 +主力净流入、涨跌家数等。内部基于 get_board_members() 获取全部成分股后求和。 + +board_symbol: 板块代码字符串,如 "881001"(酒店餐饮)。 +取自 BoardInfo.code 或 get_board_list()。 + +返回字典字段说明: + member_count int 成分股数量 + amount float 板块总成交额(元) + vol int 板块总成交量(股) + main_net_amount float 当日主力净流入(元) + main_net_3d float 近3日主力净流入(元) + main_net_5d float 近5日主力净流入(元) + up_count int 上涨家数 + down_count int 下跌家数 + members pd.DataFrame 成分股明细 +""" + +from easy_tdx import MacClient + +with MacClient.from_best_host() as c: + # 获取行业板块 881001(酒店餐饮)的汇总数据 + result = c.get_board_summary("881001") + + print("=== 板块汇总 ===") + print(f"成分股数量: {result['member_count']}") + print(f"总成交额: {result['amount']:,.0f} 元") + print(f"总成交量: {result['vol']:,} 股") + print(f"主力净流入: {result['main_net_amount']:,.0f} 元") + print(f"近3日主力: {result['main_net_3d']:,.0f} 元") + print(f"近5日主力: {result['main_net_5d']:,.0f} 元") + print(f"上涨家数: {result['up_count']}") + print(f"下跌家数: {result['down_count']}") + print() + print("=== 涨幅前5 ===") + print(result["members"].head(5).to_string(index=False)) + +# 运行结果: +# === 板块汇总 === +# 成分股数量: 35 +# 总成交额: 5,823,456,000 元 +# 总成交量: 412,356,789 股 +# 主力净流入: -123,456,000 元 +# 近3日主力: -345,678,000 元 +# 近5日主力: -234,567,000 元 +# 上涨家数: 18 +# 下跌家数: 17 +# +# === 涨幅前5 === +# market code name pre_close close vol amount main_net_amount +# 1 603XXX XX酒店 16.82 18.50 45200 80500000 1234567 +# 0 000728 华天酒店 2.96 3.25 125600 39500000 -234567 +# 0 002XXX XX文旅 14.41 15.80 32100 49200000 345678 +# 1 600XXX XX餐饮 11.26 12.30 28900 34600000 -456789 +# 0 000XXX XX酒店 8.16 8.90 56700 49800000 567890 diff --git a/pyproject.toml b/pyproject.toml index 29a8b21..d0a515e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "easy-tdx" -version = "1.1.0" +version = "1.2.0" description = "通达信 TCP 协议行情数据客户端,支持在线行情与离线本地数据读取" readme = "README.md" requires-python = ">=3.10" diff --git a/src/easy_tdx/mac/client.py b/src/easy_tdx/mac/client.py index 1097cfa..d33705d 100644 --- a/src/easy_tdx/mac/client.py +++ b/src/easy_tdx/mac/client.py @@ -11,6 +11,7 @@ from typing import Any, TypeVar import pandas as pd from .._df import _to_df +from ..codec.bitmap import Fields, PresetField from ..commands.base import BaseCommand from ..config import get_best_host, get_mac_hosts, get_port, get_timeout, save_best_host from ..exceptions import TdxConnectionError @@ -35,7 +36,6 @@ from .commands import ( from .commands.chart_sampling import ChartSamplingCmd from .commands.file_query import FileDownloadCmd, FileListCmd from .commands.goods_list import GoodsListCmd -from ..codec.bitmap import Fields, PresetField from .enums import Adjust, BoardType, Category, FilterType, Period, SortOrder, SortType from .models import ( MacBar, @@ -584,6 +584,75 @@ class MacClient: items = self._execute(SymbolBelongBoardCmd(market, code)) return _to_df(items) + def get_board_summary( + self, + board_symbol: str, + sort_type: SortType = SortType.CHANGE_PCT, + sort_order: SortOrder = SortOrder.DESC, + ) -> dict[str, Any]: + """获取板块汇总:总成交金额、主力资金流向等(聚合成分股数据)。 + + 基于 ``get_board_members`` 获取全部成分股报价,对成交额和资金流字段求和。 + + Args: + board_symbol: 板块代码(如 "881001")。 + sort_type: 排序字段。 + sort_order: 排序方向。 + + Returns: + 包含以下键的字典:: + + member_count 成分股数量 + amount 板块总成交额(元) + vol 板块总成交量(股) + main_net_amount 板块主力净流入(元) + main_net_3d 板块近3日主力净流入(元) + main_net_5d 板块近5日主力净流入(元) + up_count 上涨家数 + down_count 下跌家数 + members 成分股明细 DataFrame + """ + from ..codec.bitmap import FieldBit, PresetField + + fields = ( + PresetField.BASIC + + FieldBit.AMOUNT + + FieldBit.MAIN_NET_AMOUNT + + FieldBit.MAIN_NET_3D_AMOUNT + + FieldBit.MAIN_NET_5D_AMOUNT + ) + df = self.get_board_members( + board_symbol, + sort_type=sort_type, + sort_order=sort_order, + fields=fields, + ) + + agg_keys = ("amount", "main_net_amount", "main_net_3d_amount", "main_net_5d_amount") + numeric_cols = [c for c in agg_keys if c in df.columns] + sums = df[numeric_cols].sum() if numeric_cols else pd.Series(dtype=float) + + close_col = "close" if "close" in df.columns else None + pre_close_col = "pre_close" if "pre_close" in df.columns else None + if close_col and pre_close_col: + diff = df[close_col] - df[pre_close_col] + up_count = int((diff > 0).sum()) + down_count = int((diff < 0).sum()) + else: + up_count = down_count = 0 + + return { + "member_count": len(df), + "amount": float(sums.get("amount", 0.0)), + "vol": int(df["vol"].sum()) if "vol" in df.columns else 0, + "main_net_amount": float(sums.get("main_net_amount", 0.0)), + "main_net_3d": float(sums.get("main_net_3d_amount", 0.0)), + "main_net_5d": float(sums.get("main_net_5d_amount", 0.0)), + "up_count": up_count, + "down_count": down_count, + "members": df, + } + # ------------------------------------------------------------------ # # 资金流向 # ------------------------------------------------------------------ # @@ -1153,6 +1222,75 @@ class AsyncMacClient: items = await self._execute(SymbolBelongBoardCmd(market, code)) return _to_df(items) + async def get_board_summary( + self, + board_symbol: str, + sort_type: SortType = SortType.CHANGE_PCT, + sort_order: SortOrder = SortOrder.DESC, + ) -> dict[str, Any]: + """获取板块汇总:总成交金额、主力资金流向等(聚合成分股数据)。 + + 基于 ``get_board_members`` 获取全部成分股报价,对成交额和资金流字段求和。 + + Args: + board_symbol: 板块代码(如 "881001")。 + sort_type: 排序字段。 + sort_order: 排序方向。 + + Returns: + 包含以下键的字典:: + + member_count 成分股数量 + amount 板块总成交额(元) + vol 板块总成交量(股) + main_net_amount 板块主力净流入(元) + main_net_3d 板块近3日主力净流入(元) + main_net_5d 板块近5日主力净流入(元) + up_count 上涨家数 + down_count 下跌家数 + members 成分股明细 DataFrame + """ + from ..codec.bitmap import FieldBit, PresetField + + fields = ( + PresetField.BASIC + + FieldBit.AMOUNT + + FieldBit.MAIN_NET_AMOUNT + + FieldBit.MAIN_NET_3D_AMOUNT + + FieldBit.MAIN_NET_5D_AMOUNT + ) + df = await self.get_board_members( + board_symbol, + sort_type=sort_type, + sort_order=sort_order, + fields=fields, + ) + + agg_keys = ("amount", "main_net_amount", "main_net_3d_amount", "main_net_5d_amount") + numeric_cols = [c for c in agg_keys if c in df.columns] + sums = df[numeric_cols].sum() if numeric_cols else pd.Series(dtype=float) + + close_col = "close" if "close" in df.columns else None + pre_close_col = "pre_close" if "pre_close" in df.columns else None + if close_col and pre_close_col: + diff = df[close_col] - df[pre_close_col] + up_count = int((diff > 0).sum()) + down_count = int((diff < 0).sum()) + else: + up_count = down_count = 0 + + return { + "member_count": len(df), + "amount": float(sums.get("amount", 0.0)), + "vol": int(df["vol"].sum()) if "vol" in df.columns else 0, + "main_net_amount": float(sums.get("main_net_amount", 0.0)), + "main_net_3d": float(sums.get("main_net_3d_amount", 0.0)), + "main_net_5d": float(sums.get("main_net_5d_amount", 0.0)), + "up_count": up_count, + "down_count": down_count, + "members": df, + } + # ------------------------------------------------------------------ # # 资金流向 # ------------------------------------------------------------------ #