mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 16:54:20 +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
@@ -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