diff --git a/README.md b/README.md index 17769fe..0fe7ae8 100644 --- a/README.md +++ b/README.md @@ -785,6 +785,14 @@ ruff format --check src/ tests/ # format check ## Changelog +### 1.6.1 (2026-06-07) + +**Bug 修复** — 修复 sync-all/sync-daily 对指数文件误用股票解析器导致垃圾日期的问题。 + +- 修复 `_fetch_all_daily_bars` 对指数文件(sh00/sh88/sh99, sz39)错误调用 `get_security_bars()` 的问题 +- 指数文件现在正确使用 `get_index_bars()`(服务端响应每条记录多 4 字节上涨/下跌家数) +- 新增 `_is_index_code()` 辅助函数,根据市场和代码前缀判断证券类型 + ### 1.6.0 (2026-06-07) **离线数据写入同步** — 从服务端获取最新日线数据并写入本地通达信 .day 文件,替代通达信内置下载功能。 diff --git a/pyproject.toml b/pyproject.toml index 4e3805d..2aa5b53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "easy-tdx" -version = "1.6.0" +version = "1.6.1" description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步" readme = "README.md" requires-python = ">=3.10" diff --git a/src/easy_tdx/cli/cmd_offline.py b/src/easy_tdx/cli/cmd_offline.py index eddf56b..2f83cde 100644 --- a/src/easy_tdx/cli/cmd_offline.py +++ b/src/easy_tdx/cli/cmd_offline.py @@ -459,8 +459,26 @@ def _df_to_bars(df: pd.DataFrame) -> list[SecurityBar]: return bars +def _is_index_code(exchange: str, code: str) -> bool: + """根据市场和代码前缀判断是否为指数。 + + 指数需要调用 get_index_bars()(服务端响应每条记录多 4 字节), + 而非 get_security_bars()。 + """ + head = code[:2] + if exchange == "sh": + return head in ("00", "88", "99") + if exchange == "sz": + return head == "39" + return False + + def _fetch_all_daily_bars( - client: TdxClient, market: int, code: str, need_full: bool = False + client: TdxClient, + market: int, + code: str, + need_full: bool = False, + is_index: bool = False, ) -> list[SecurityBar]: """从服务端分页获取全部日线数据。 @@ -470,19 +488,22 @@ def _fetch_all_daily_bars( code: 6 位股票代码。 need_full: True 表示拉取全量历史(空文件场景), False 表示只拉最近一页(增量更新)。 + is_index: True 表示指数,使用 get_index_bars()。 Returns: SecurityBar 列表(按日期升序)。 """ from ..models.enums import KlineCategory + fetch_fn = client.get_index_bars if is_index else client.get_security_bars + all_bars: list[SecurityBar] = [] start = 0 page_size = 800 max_pages = 50 if need_full else 1 # 50 页 = 40000 条,足够覆盖 A 股全部历史 for _ in range(max_pages): - df = client.get_security_bars(market, code, KlineCategory.DAY, start, page_size) + df = fetch_fn(market, code, KlineCategory.DAY, start, page_size) if df.empty: break all_bars.extend(_df_to_bars(df)) @@ -517,8 +538,11 @@ def _sync_one_daily(client: TdxClient, filepath: Path) -> tuple[int, str]: last_date = get_last_bar_date(filepath) need_full = last_date is None + # 判断是否为指数(指数需要 get_index_bars,响应格式不同) + is_index = _is_index_code(exchange, code) + # 从服务端分页获取日线 - bars = _fetch_all_daily_bars(client, market, code, need_full=need_full) + bars = _fetch_all_daily_bars(client, market, code, need_full=need_full, is_index=is_index) if not bars: return 0, "服务端无数据"