mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 14:34:15 +08:00
feat: add get_board_ranking() for board change-pct ranking
New MacClient/AsyncMacClient method that ranks all boards of a given type (industry/concept) by change_pct, amount, main_net_amount, or vol. Aggregates member quotes via get_board_summary() for each board. Also bumps version to 1.3.0 and updates README + CLI version. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2ec36e01bf
commit
72652b9f8b
@@ -222,6 +222,11 @@ with MacClient.from_best_host() as c:
|
||||
# "down_count": 37,
|
||||
# "members": DataFrame(...), # 成分股明细
|
||||
# }
|
||||
|
||||
# 板块涨跌幅排行榜
|
||||
df = c.get_board_ranking(BoardType.HY, top_n=10, sort_by="change_pct")
|
||||
df = c.get_board_ranking(BoardType.GN, top_n=20, sort_by="main_net_amount")
|
||||
# 返回列:code, name, change_pct, amount, vol, main_net_amount, up_count, down_count, member_count
|
||||
```
|
||||
|
||||
#### 资金流向
|
||||
@@ -404,6 +409,7 @@ bars = read_daily_bars(filepath)
|
||||
| `get_board_list(board_type, ...)` | 板块列表 |
|
||||
| `get_board_members(board_symbol, ...)` | 板块成分股报价 |
|
||||
| `get_board_summary(board_symbol, ...)` | 板块汇总(成交额、主力净流入、涨跌家数) |
|
||||
| `get_board_ranking(board_type, top_n, sort_by, ...)` | 板块涨跌幅排行榜(行业/概念排行) |
|
||||
| `get_belong_board(market, code)` | 个股所属板块 |
|
||||
| `get_capital_flow(market, code)` | 资金流向 |
|
||||
| `get_auction(market, code)` | 集合竞价 |
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""演示:板块涨跌幅排行榜。
|
||||
|
||||
通过 MacClient 的 get_board_ranking() 获取行业或概念板块的聚合排行数据,
|
||||
包含涨跌幅、成交额、成交量、主力净流入、涨跌家数等。
|
||||
|
||||
board_type 参数:
|
||||
BoardType.HY — 行业板块
|
||||
BoardType.GN — 概念板块
|
||||
|
||||
返回 DataFrame 列:
|
||||
code 板块代码
|
||||
name 板块名称
|
||||
change_pct 涨跌幅%
|
||||
amount 板块总成交额(元)
|
||||
vol 板块总成交量(股)
|
||||
main_net_amount 板块主力净流入(元)
|
||||
up_count 上涨家数
|
||||
down_count 下跌家数
|
||||
member_count 成分股数量
|
||||
"""
|
||||
|
||||
from easy_tdx import MacClient
|
||||
from easy_tdx.mac.enums import BoardType
|
||||
|
||||
with MacClient.from_best_host() as c:
|
||||
# 行业板块涨幅
|
||||
print("=== 行业板块涨幅 ===")
|
||||
df_hy = c.get_board_ranking(BoardType.HY, top_n=300, sort_by="change_pct")
|
||||
print(df_hy.to_string(index=False))
|
||||
|
||||
print()
|
||||
|
||||
# 概念板块主力净流入
|
||||
print("=== 概念板块主力净流入 ===")
|
||||
df_gn = c.get_board_ranking(BoardType.GN, top_n=300, sort_by="main_net_amount")
|
||||
print(df_gn.to_string(index=False))
|
||||
|
||||
# 运行结果示例:
|
||||
# === 行业板块涨幅 ===
|
||||
# code name change_pct amount vol ...
|
||||
# 881127 通信设备 3.25 18523456000 1234567890 ...
|
||||
# 881156 半导体 2.98 25678900000 2345678901 ...
|
||||
# ...
|
||||
#
|
||||
# === 概念板块主力净流入 ===
|
||||
# code name change_pct amount vol ...
|
||||
# 880952 人工智能 1.56 42345678000 3456789012 ...
|
||||
# 880930 芯片概念 1.23 38765432000 2987654321 ...
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "easy-tdx"
|
||||
version = "1.2.1"
|
||||
version = "1.3.0"
|
||||
description = "通达信 TCP 协议行情数据客户端,支持在线行情与离线本地数据读取"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -107,4 +107,4 @@ __all__ = [
|
||||
"save_best_ex_host",
|
||||
]
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.3.0"
|
||||
|
||||
@@ -19,7 +19,7 @@ from .cmd_transaction import transaction
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version="1.1.0", prog_name="easy-tdx")
|
||||
@click.version_option(version="1.3.0", prog_name="easy-tdx")
|
||||
def cli() -> None:
|
||||
"""easy-tdx -- 通达信行情数据 CLI(默认 JSON 输出,适合 Agent 使用)。
|
||||
|
||||
|
||||
+153
-12
@@ -76,6 +76,8 @@ def _convert_board_code(board_symbol: str) -> int:
|
||||
if s.startswith("000"):
|
||||
return 31000 + int(s)
|
||||
return int(s)
|
||||
|
||||
|
||||
_TRANSACTION_PAGE_SIZE = 1000
|
||||
|
||||
_T = TypeVar("_T")
|
||||
@@ -393,8 +395,7 @@ class MacClient:
|
||||
from datetime import date as date_cls
|
||||
|
||||
query_date = (
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100)
|
||||
if date is not None else None
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100) if date is not None else None
|
||||
)
|
||||
chart = self._execute(SymbolTickChartCmd(market, code, query_date))
|
||||
return pd.DataFrame(_flatten_tick_chart(chart))
|
||||
@@ -417,8 +418,7 @@ class MacClient:
|
||||
from datetime import date as date_cls
|
||||
|
||||
start_date = (
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100)
|
||||
if date is not None else None
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100) if date is not None else None
|
||||
)
|
||||
chart = self._execute(TickChartsCmd(market, code, start_date, days))
|
||||
return pd.DataFrame(_flatten_multi_tick_chart(chart))
|
||||
@@ -457,8 +457,7 @@ class MacClient:
|
||||
from datetime import date as date_cls
|
||||
|
||||
query_date = (
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100)
|
||||
if date is not None else None
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100) if date is not None else None
|
||||
)
|
||||
all_items = self._execute(
|
||||
SymbolTransactionCmd(
|
||||
@@ -653,6 +652,80 @@ class MacClient:
|
||||
"members": df,
|
||||
}
|
||||
|
||||
def get_board_ranking(
|
||||
self,
|
||||
board_type: BoardType = BoardType.HY,
|
||||
top_n: int = 50,
|
||||
sort_by: str = "change_pct",
|
||||
ascending: bool = False,
|
||||
) -> pd.DataFrame:
|
||||
"""获取板块涨跌幅排行榜(含成交额、成交量、资金流入流出、涨跌家数)。
|
||||
|
||||
先通过 ``get_board_list`` 获取全部板块,再逐个调用
|
||||
``get_board_summary`` 聚合成分股数据,合并为排行榜 DataFrame。
|
||||
|
||||
Args:
|
||||
board_type: 板块类型(``BoardType.HY`` 行业 / ``BoardType.GN`` 概念)。
|
||||
top_n: 聚合的板块数量上限。概念板块有 300+ 个,
|
||||
全部聚合网络开销大,建议按需限制。
|
||||
sort_by: 排序字段,可选 ``change_pct`` / ``amount``
|
||||
/ ``main_net_amount`` / ``vol``。
|
||||
ascending: 排序方向,默认降序。
|
||||
|
||||
Returns:
|
||||
DataFrame,列::
|
||||
|
||||
code 板块代码
|
||||
name 板块名称
|
||||
change_pct 涨跌幅%
|
||||
amount 板块总成交额(元)
|
||||
vol 板块总成交量(股)
|
||||
main_net_amount 板块主力净流入(元)
|
||||
up_count 上涨家数
|
||||
down_count 下跌家数
|
||||
member_count 成分股数量
|
||||
"""
|
||||
_VALID_SORT = {"change_pct", "amount", "main_net_amount", "vol"}
|
||||
if sort_by not in _VALID_SORT:
|
||||
raise ValueError(f"sort_by 必须是 {_VALID_SORT} 之一, got {sort_by!r}")
|
||||
|
||||
boards_df = self.get_board_list(board_type)
|
||||
if boards_df.empty:
|
||||
return pd.DataFrame()
|
||||
|
||||
# 从 board_list 的 price / pre_close 计算涨跌幅
|
||||
if "price" in boards_df.columns and "pre_close" in boards_df.columns:
|
||||
pre = boards_df["pre_close"].replace(0, float("nan"))
|
||||
boards_df["change_pct"] = (boards_df["price"] - boards_df["pre_close"]) / pre * 100
|
||||
else:
|
||||
boards_df["change_pct"] = 0.0
|
||||
|
||||
# 按涨跌幅初排,取 top_n 减少后续聚合开销
|
||||
boards_df = boards_df.sort_values("change_pct", ascending=ascending).head(top_n)
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
for _, row in boards_df.iterrows():
|
||||
code = str(row["code"])
|
||||
summary = self.get_board_summary(code)
|
||||
rows.append(
|
||||
{
|
||||
"code": code,
|
||||
"name": row.get("name", ""),
|
||||
"change_pct": round(float(row.get("change_pct", 0.0)), 2),
|
||||
"amount": summary["amount"],
|
||||
"vol": summary["vol"],
|
||||
"main_net_amount": summary["main_net_amount"],
|
||||
"up_count": summary["up_count"],
|
||||
"down_count": summary["down_count"],
|
||||
"member_count": summary["member_count"],
|
||||
}
|
||||
)
|
||||
|
||||
result = pd.DataFrame(rows)
|
||||
if not result.empty:
|
||||
result = result.sort_values(sort_by, ascending=ascending).reset_index(drop=True)
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 资金流向
|
||||
# ------------------------------------------------------------------ #
|
||||
@@ -1080,8 +1153,7 @@ class AsyncMacClient:
|
||||
from datetime import date as date_cls
|
||||
|
||||
query_date = (
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100)
|
||||
if date is not None else None
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100) if date is not None else None
|
||||
)
|
||||
chart = await self._execute(SymbolTickChartCmd(market, code, query_date))
|
||||
return pd.DataFrame(_flatten_tick_chart(chart))
|
||||
@@ -1096,8 +1168,7 @@ class AsyncMacClient:
|
||||
from datetime import date as date_cls
|
||||
|
||||
start_date = (
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100)
|
||||
if date is not None else None
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100) if date is not None else None
|
||||
)
|
||||
chart = await self._execute(TickChartsCmd(market, code, start_date, days))
|
||||
return pd.DataFrame(_flatten_multi_tick_chart(chart))
|
||||
@@ -1121,8 +1192,7 @@ class AsyncMacClient:
|
||||
from datetime import date as date_cls
|
||||
|
||||
query_date = (
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100)
|
||||
if date is not None else None
|
||||
date_cls(date // 10000, (date % 10000) // 100, date % 100) if date is not None else None
|
||||
)
|
||||
all_items = await self._execute(
|
||||
SymbolTransactionCmd(
|
||||
@@ -1291,6 +1361,77 @@ class AsyncMacClient:
|
||||
"members": df,
|
||||
}
|
||||
|
||||
async def get_board_ranking(
|
||||
self,
|
||||
board_type: BoardType = BoardType.HY,
|
||||
top_n: int = 50,
|
||||
sort_by: str = "change_pct",
|
||||
ascending: bool = False,
|
||||
) -> pd.DataFrame:
|
||||
"""获取板块涨跌幅排行榜(含成交额、成交量、资金流入流出、涨跌家数)。
|
||||
|
||||
先通过 ``get_board_list`` 获取全部板块,再并发调用
|
||||
``get_board_summary`` 聚合成分股数据,合并为排行榜 DataFrame。
|
||||
|
||||
Args:
|
||||
board_type: 板块类型(``BoardType.HY`` 行业 / ``BoardType.GN`` 概念)。
|
||||
top_n: 聚合的板块数量上限。概念板块有 300+ 个,
|
||||
全部聚合网络开销大,建议按需限制。
|
||||
sort_by: 排序字段,可选 ``change_pct`` / ``amount``
|
||||
/ ``main_net_amount`` / ``vol``。
|
||||
ascending: 排序方向,默认降序。
|
||||
|
||||
Returns:
|
||||
DataFrame,列::
|
||||
|
||||
code 板块代码
|
||||
name 板块名称
|
||||
change_pct 涨跌幅%
|
||||
amount 板块总成交额(元)
|
||||
vol 板块总成交量(股)
|
||||
main_net_amount 板块主力净流入(元)
|
||||
up_count 上涨家数
|
||||
down_count 下跌家数
|
||||
member_count 成分股数量
|
||||
"""
|
||||
_VALID_SORT = {"change_pct", "amount", "main_net_amount", "vol"}
|
||||
if sort_by not in _VALID_SORT:
|
||||
raise ValueError(f"sort_by 必须是 {_VALID_SORT} 之一, got {sort_by!r}")
|
||||
|
||||
boards_df = await self.get_board_list(board_type)
|
||||
if boards_df.empty:
|
||||
return pd.DataFrame()
|
||||
|
||||
if "price" in boards_df.columns and "pre_close" in boards_df.columns:
|
||||
pre = boards_df["pre_close"].replace(0, float("nan"))
|
||||
boards_df["change_pct"] = (boards_df["price"] - boards_df["pre_close"]) / pre * 100
|
||||
else:
|
||||
boards_df["change_pct"] = 0.0
|
||||
|
||||
boards_df = boards_df.sort_values("change_pct", ascending=ascending).head(top_n)
|
||||
|
||||
async def _fetch_row(row: pd.Series) -> dict[str, Any]:
|
||||
code = str(row["code"])
|
||||
summary = await self.get_board_summary(code)
|
||||
return {
|
||||
"code": code,
|
||||
"name": row.get("name", ""),
|
||||
"change_pct": round(float(row.get("change_pct", 0.0)), 2),
|
||||
"amount": summary["amount"],
|
||||
"vol": summary["vol"],
|
||||
"main_net_amount": summary["main_net_amount"],
|
||||
"up_count": summary["up_count"],
|
||||
"down_count": summary["down_count"],
|
||||
"member_count": summary["member_count"],
|
||||
}
|
||||
|
||||
rows = await asyncio.gather(*[_fetch_row(row) for _, row in boards_df.iterrows()])
|
||||
|
||||
result = pd.DataFrame(rows)
|
||||
if not result.empty:
|
||||
result = result.sort_values(sort_by, ascending=ascending).reset_index(drop=True)
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 资金流向
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
Reference in New Issue
Block a user