From bc83ffa4ac6086c47c7aac3e85fbff4c445124b1 Mon Sep 17 00:00:00 2001 From: GitHub Date: Wed, 1 Jul 2026 16:22:57 +0800 Subject: [PATCH] =?UTF-8?q?fix(tick=5Fcharts):=20=E5=A4=9A=E6=97=A5?= =?UTF-8?q?=E5=88=86=E6=97=B6=E5=9B=BE=20minutes>=3D1440=20=E6=8A=A5=20Val?= =?UTF-8?q?ueError=20(Issue=20#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++ pyproject.toml | 2 +- src/easy_tdx/mac/commands/tick_charts.py | 5 +- tests/unit/test_mac_tick_charts.py | 111 +++++++++++++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 tests/unit/test_mac_tick_charts.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e47a215..0adafdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ 本文件记录 easy-tdx 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/)。 +## [1.16.1] — 2026-07-01 + +### 修复 + +- **多日分时图 `tick --days N` 命令因 `minutes ≥ 1440` 报 `ValueError: hour must be in 0..23`**(`mac/commands/tick_charts.py`,[Issue #10](https://github.com/handsomejustin/easy_tdx/issues/10))— 执行 `easy-tdx tick SH 600519 --days 5` 时崩溃。多日分时图(MAC 协议 `0x123E`)解析 `time(minutes // 60, minutes % 60)` 缺少对 24 取模的保护,当个别服务器 / 数据状态下返回的 `minutes` 值 ≥ 1440(累计或异常值,用户实测出现 `minutes ≈ 62340` 即 `// 60 == 1039`)时,`minutes // 60` 超过 23 触发 `ValueError`。 + - 修复:改为 `time(minutes // 60 % 24, minutes % 60)`,与单日分时 `SymbolTickChartCmd` 的处理**完全一致**。 + - 语义自洽:每条 tick 的**日期**取自 `date_ints[d]`(与 `minutes` 无关),`minutes` 字段只承载「日内时刻」,`% 24` 折算成日内时刻是正确的降级。 + - **对正常数据零行为改变**:抓取多只股票 × {2 天, 5 天} 真实响应逐条对比,新公式与旧公式产出 `time` 对象**完全相同**(`new_vs_old_diffs=0`),所有时刻落在 09–15 交易时段。 + - 对异常 `minutes` 值,无法仅凭该字段恢复真正时刻(需日边界信息),故产出合法占位时刻,避免崩溃、不污染日期列。 + - 新增 3 个单元测试(`test_mac_tick_charts.py`:正常分钟 / Issue #10 回归用报错现场原值 62340 / 请求包布局),全量 703 单测通过。 + ## [1.16.0] — 2026-06-30 ### 新增 diff --git a/pyproject.toml b/pyproject.toml index 665655e..fc457f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "easy-tdx" -version = "1.16.0" +version = "1.16.1" description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步" readme = "README.md" requires-python = ">=3.10" diff --git a/src/easy_tdx/mac/commands/tick_charts.py b/src/easy_tdx/mac/commands/tick_charts.py index 6399a80..b3e4d04 100644 --- a/src/easy_tdx/mac/commands/tick_charts.py +++ b/src/easy_tdx/mac/commands/tick_charts.py @@ -72,7 +72,10 @@ class TickChartsCmd(BaseCommand[MacMultiTickChart]): ) ticks.append( MacTick( - time=time(minutes // 60, minutes % 60), + # 多日分时里 minutes 在个别服务器/数据状态下可能 ≥ 1440 + # (累计或异常值),对 24 取模折算成日内时刻,避免 ValueError。 + # 与单日分时 SymbolTickChartCmd 的处理保持一致(Issue #10)。 + time=time(minutes // 60 % 24, minutes % 60), price=price, avg=avg, vol=vol, diff --git a/tests/unit/test_mac_tick_charts.py b/tests/unit/test_mac_tick_charts.py new file mode 100644 index 0000000..bd544cb --- /dev/null +++ b/tests/unit/test_mac_tick_charts.py @@ -0,0 +1,111 @@ +"""MAC 多日分时图(0x123E)解析测试。 + +包含 Issue #10 回归:当服务器返回的 minutes ≥ 1440(个别服务器累计或异常值) +时,parse_response 不应抛 ValueError,而应按 % 24 折算为日内时刻。 +""" + +import struct + +from easy_tdx.mac.commands.tick_charts import TickChartsCmd +from easy_tdx.models.enums import Market + + +def _build_multi_tick_body( + days: int, + page_size: int, + minutes_grid: list[list[int]], +) -> bytes: + """构造一个多日分时图响应 body。 + + Args: + days: 天数(写入 count)。 + page_size: 每天分时点数。 + minutes_grid: 长度 days 的列表,每个元素是长度 page_size 的 minutes 值列表。 + """ + # 头部: market(2) + code(22) + body = bytearray(struct.pack(" 62340//60 % 24 = 1039 % 24 = 7 时, 62340 % 60 = 0 分 + assert str(chart.charts[0].ticks[1].time) == "07:00:00" + # 正常点不受影响 + assert str(chart.charts[0].ticks[0].time) == "09:30:00" + assert str(chart.charts[1].ticks[3].time) == "14:59:00" + + +def test_multi_tick_charts_request_layout(): + """验证请求包布局与文档一致:market + code(22) + start_ymd + days + 1。""" + from datetime import date as date_cls + + cmd = TickChartsCmd(int(Market.SH), "600519", date_cls(2025, 1, 15), 5) + req = cmd.build_request() + # MAC 帧前缀由 build_mac_request 生成,body 在其后;校验 body 嵌入其中 + expected_body = struct.pack( + "