diff --git a/CHANGELOG.md b/CHANGELOG.md index bf211ee..4cc8545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ 本文件记录 easy-tdx 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/)。 +## [Unreleased] + +### 新增 + +- **通达信原生 F10 与财务快照 CLI 命令** — 把 `TdxClient` 上已封装但未暴露给 CLI 的三个方法做成命令,数据源与 Web 层 `/finance` `/company/*` 端点同源,覆盖 `f10`(新浪三表)之外的 F10 全文板块。 + - 新增 `easy-tdx finance-info` — 最新财务快照(37 字段:股本结构、资产负债、利润、现金流、每股指标),与 `f10`(多期三表)互补。 + - 新增 `easy-tdx company-info` — F10 板块目录,列出最新提示/公司概况/财务分析/股东研究/股本结构/资本运作/业内点评/行业分析/公司大事/研究报告/经营分析/主力追踪/分红扩股/高层治理/龙虎榜单/关联个股等板块及其文件偏移。 + - 新增 `easy-tdx company-info-content` — 读取 F10 板块正文,`name_or_filename` 既可传板块名(自动定位到该板块起点读取),也可直接传文件名;`--offset` / `--length` 控制读取范围。 + - 新增 `get_tdx_client()` 上下文管理器(`cli/conn.py`),仿 `get_mac_client()` 包装 `TdxClient.from_best_host()`。 + ## [1.15.0] — 2026-06-25 ### 新增 diff --git a/README.md b/README.md index 0547d78..f929a26 100644 --- a/README.md +++ b/README.md @@ -833,6 +833,22 @@ easy-tdx f10 000001 --type llb --table # 平安现金流量表,表格输 > 新浪财经数据源,``--type`` 支持 ``lrb``(利润表)/``fzb``(资产负债表)/``llb``(现金流量表)。 > 独立于 TDX 行情服务器,``item_value`` 已转 float 可直接数值计算,同比附 ``{科目}_同比`` 列。 +### 通达信原生 F10 与最新财务快照 + +走通达信协议(与 Web 层 ``/finance`` ``/company/*`` 端点同源),覆盖 ``f10``(新浪三表)之外的 F10 全文板块: + +```bash +easy-tdx finance-info SH 600519 --table # 最新财务快照(30+ 项单期指标) +easy-tdx company-info SH 600519 # F10 板块目录(最新提示/公司概况/...) +easy-tdx company-info-content SH 600519 "公司概况" # 读板块正文,自动解析板块名→文件 +easy-tdx company-info-content SH 600519 "分红扩股" --length 2048 # 加长读取 +easy-tdx company-info-content SH 600519 600519.txt # 也可直接传文件名 +``` + +- ``finance-info``:最新一期财务快照,含股本结构、资产负债、利润、现金流、每股指标(37 字段)。与 ``f10`` 互补——前者是单期快照,后者是多期三表。 +- ``company-info``:列出该股 F10 的全部板块(最新提示、公司概况、财务分析、股东研究、股本结构、资本运作、业内点评、行业分析、公司大事、研究报告、经营分析、主力追踪、分红扩股、高层治理、龙虎榜单、关联个股)及其文件偏移。 +- ``company-info-content``:``name_or_filename`` 既可传板块名(自动定位到该板块起点读取),也可直接传文件名。``--offset`` / ``--length`` 控制读取范围(字节,默认 0/1024)。 + ### 扩展市场(港股/美股/期货) ```bash @@ -1070,6 +1086,9 @@ uvicorn.run(app, host="0.0.0.0", port=8000) | `screen rank` | 扫描结果回测排名(按夏普/回撤等指标排序) | | `serve` | 启动 Web API 服务器(REST + WebSocket,需 `easy-tdx[web]`) | | `f10` | 财报三表(新浪:利润表/资产负债表/现金流量表) | +| `finance-info` | 最新财务快照(通达信协议,30+ 项单期指标) | +| `company-info` | F10 公司信息板块目录(通达信协议) | +| `company-info-content` | F10 板块正文(板块名或文件名,GBK 文本) | | `fund-flow` | 历史资金流向 | | `ex kline` | 扩展市场 K 线 | | `ex quote` | 扩展市场报价 | diff --git a/src/easy_tdx/cli/__init__.py b/src/easy_tdx/cli/__init__.py index bd833eb..a2f3e6c 100644 --- a/src/easy_tdx/cli/__init__.py +++ b/src/easy_tdx/cli/__init__.py @@ -19,6 +19,7 @@ from .cmd_board import ( ) from .cmd_capital import capital_flow from .cmd_chanlun import chanlun +from .cmd_company import company_info, company_info_content, finance_info from .cmd_ex import ex from .cmd_factor import factor from .cmd_finance import f10, fund_flow @@ -80,6 +81,9 @@ cli.add_command(server_info) cli.add_command(symbol_info) cli.add_command(f10) cli.add_command(fund_flow) +cli.add_command(finance_info) +cli.add_command(company_info) +cli.add_command(company_info_content) cli.add_command(ex) cli.add_command(indicator) cli.add_command(indicator_list) diff --git a/src/easy_tdx/cli/cmd_company.py b/src/easy_tdx/cli/cmd_company.py new file mode 100644 index 0000000..3443a4f --- /dev/null +++ b/src/easy_tdx/cli/cmd_company.py @@ -0,0 +1,165 @@ +"""通达信原生 F10 / 财务快照命令(TDX 协议,与 Web 层 ``/finance`` ``/company/*`` 同源)。 + +与 ``cmd_finance.py`` 的新浪三表(``f10``)区别: + - ``f10`` 走新浪 HTTP,输出多期结构化利润表/负债表/现金流 + - 本模块走通达信协议,输出最新一期财务快照 + F10 全文板块 +""" + +from __future__ import annotations + +import click + +from ..models.enums import Market +from .parsers import parse_market + + +@click.command("finance-info") +@click.argument("market") +@click.argument("code") +@click.option("--table", "use_table", is_flag=True, help="表格输出") +@click.option("--output", "output_fmt", type=click.Choice(["json", "table", "csv"]), default="json") +def finance_info(market: str, code: str, use_table: bool, output_fmt: str) -> None: + """获取最新财务快照(通达信协议,30+ 项单期指标)。 + + 含股本结构、资产负债、利润、现金流、每股指标等最新一期数据。 + 与 ``f10``(新浪多期三表)互补,本命令仅返回最新一期。 + + \b + 示例: + + easy-tdx finance-info SH 600519 --table + + easy-tdx finance-info SZ 000001 + """ + from ..exceptions import TdxError + from .conn import get_tdx_client + from .output import print_error, print_output + + fmt = "table" if use_table else output_fmt + mkt = Market(parse_market(market)) + try: + with get_tdx_client() as client: + df = client.get_finance_info(mkt, code) + except TdxError as e: + print_error(str(e)) + raise SystemExit(1) from e + print_output(df, fmt) + + +@click.command("company-info") +@click.argument("market") +@click.argument("code") +@click.option("--table", "use_table", is_flag=True, help="表格输出") +@click.option("--output", "output_fmt", type=click.Choice(["json", "table", "csv"]), default="json") +def company_info(market: str, code: str, use_table: bool, output_fmt: str) -> None: + """获取 F10 公司信息板块目录。 + + 返回各板块的名称(最新提示/公司概况/财务分析/持股情况/股本结构/ + 分红融资/高管信息/行业产品/新闻公告/回顾展望 等)、文件名及偏移长度。 + 拿到板块名后可用 ``company-info-content`` 读取正文。 + + \b + 示例: + + easy-tdx company-info SH 600519 --table + """ + from ..exceptions import TdxError + from .conn import get_tdx_client + from .output import print_error, print_output + + fmt = "table" if use_table else output_fmt + mkt = Market(parse_market(market)) + try: + with get_tdx_client() as client: + df = client.get_company_info_category(mkt, code) + except TdxError as e: + print_error(str(e)) + raise SystemExit(1) from e + print_output(df, fmt) + + +@click.command("company-info-content") +@click.argument("market") +@click.argument("code") +@click.argument("name_or_filename") +@click.option( + "--offset", + default=0, + type=int, + help="偏移(字节):传板块名时为板块内相对偏移,传文件名时为文件绝对偏移,默认 0", +) +@click.option("--length", default=1024, type=int, help="读取长度(字节,默认 1024)") +def company_info_content( + market: str, code: str, name_or_filename: str, offset: int, length: int +) -> None: + """读取 F10 公司信息板块正文(GBK 文本)。 + + ``name_or_filename`` 既可传板块名(如 ``最新提示``,自动查目录解析), + 也可直接传文件名(如 ``600519.txt``)。建议先用 ``company-info`` + 查看可用板块名。 + + ``--offset`` 语义随入参而定:传板块名时为**板块内相对偏移** + (0 = 板块起点);传文件名时为**文件绝对偏移**。 + + \b + 示例: + + easy-tdx company-info-content SH 600519 "最新提示" + + easy-tdx company-info-content SH 600519 600519.txt --length 2048 + """ + from ..exceptions import TdxError + from .conn import get_tdx_client + from .output import print_error + + mkt = Market(parse_market(market)) + try: + with get_tdx_client() as client: + filename, seg_start, matched_board = _resolve_filename( + client, mkt, code, name_or_filename + ) + # 板块名命中:--offset 解释为板块内相对偏移(默认 0 = 板块起点) + # 文件名:--offset 解释为文件绝对偏移 + effective_offset = seg_start + offset if matched_board else offset + content = client.get_company_info_content(mkt, code, filename, effective_offset, length) + except _ResolveError as e: + print_error(str(e)) + raise SystemExit(1) from e + except TdxError as e: + print_error(str(e)) + raise SystemExit(1) from e + click.echo(content) + + +class _ResolveError(Exception): + """板块名解析失败(内部信号异常,与 TdxError 分开捕获)。""" + + +def _resolve_filename( + client: object, market: Market, code: str, name_or_filename: str +) -> tuple[str, int, bool]: + """把板块名或文件名解析为 (filename, 板块起始 offset, 是否板块名命中)。 + + 策略:先查 F10 目录,若 ``name_or_filename`` 命中某个板块名则返回 + (对应 filename, 该板块 start, True);否则视为文件名返回 (name_or_filename, 0, False)。 + 返回的 ``matched`` 决定 ``--offset`` 的语义:板块名时为相对偏移,文件名时为绝对偏移。 + """ + # 形如 '600519.txt' 的文件名,跳过目录查询直接用(offset 为绝对偏移) + if "." in name_or_filename: + return name_or_filename, 0, False + + df = client.get_company_info_category(market, code) # type: ignore[attr-defined] + if not df.empty: + row = df.loc[df["name"] == name_or_filename] + if not row.empty: + return str(row["filename"].iloc[0]), int(row["start"].iloc[0]), True + + # 未命中板块名:当作不带后缀的纯 ASCII 文件名兜底(如 '600519' → '600519.txt') + if name_or_filename.isascii() and name_or_filename.isalnum(): + return f"{name_or_filename}.txt", 0, False + + available = "" if df.empty else "、".join(df["name"].tolist()) + raise _ResolveError( + f"未找到板块名 '{name_or_filename}'。" + + (f"可用板块:{available}" if available else "请先运行 `easy-tdx company-info` 查看目录。") + ) diff --git a/src/easy_tdx/cli/conn.py b/src/easy_tdx/cli/conn.py index 4259da6..8c93ea7 100644 --- a/src/easy_tdx/cli/conn.py +++ b/src/easy_tdx/cli/conn.py @@ -1,10 +1,11 @@ -"""CLI 连接工厂:延迟创建 MAC 客户端。""" +"""CLI 连接工厂:延迟创建 MAC / TDX 客户端。""" from __future__ import annotations from collections.abc import Generator from contextlib import contextmanager +from ..client import TdxClient from ..ex.mac_client import MacExClient from ..mac.client import MacClient @@ -26,6 +27,26 @@ def get_mac_client() -> Generator[MacClient, None, None]: client.close() +@contextmanager +def get_tdx_client() -> Generator[TdxClient, None, None]: + """创建 TDX 客户端上下文(自动选最快服务器)。 + + 用于通达信原生协议命令(F10 公司信息、最新财务快照等), + 与 Web 层 ``/finance`` ``/company/*`` 端点同源。 + + 使用方式:: + + with get_tdx_client() as client: + df = client.get_finance_info(market, code) + """ + client = TdxClient.from_best_host() + try: + client.connect() + yield client + finally: + client.close() + + @contextmanager def get_mac_ex_client() -> Generator[MacExClient, None, None]: """创建扩展市场 MAC 客户端上下文(端口 7727)。"""