diff --git a/src/xmtdx/client.py b/src/xmtdx/client.py index 2a3bae9..534e1d4 100644 --- a/src/xmtdx/client.py +++ b/src/xmtdx/client.py @@ -1,9 +1,11 @@ """高层行情 API:TdxClient(同步)和 AsyncTdxClient(asyncio)。""" import asyncio +from datetime import datetime from collections.abc import Awaitable, Callable from types import TracebackType from typing import TypeVar +from zoneinfo import ZoneInfo from .codec.block import parse_block_dat from .codec.industry import parse_tdxhy_cfg @@ -34,6 +36,11 @@ from .transport.sync import KNOWN_HOSTS, TdxConnection, ping_all _DEFAULT_PORT = 7709 _T = TypeVar("_T") +_SHANGHAI_TZ = ZoneInfo("Asia/Shanghai") + + +def _today_in_shanghai() -> int: + return int(datetime.now(_SHANGHAI_TZ).strftime("%Y%m%d")) def _record_signature( @@ -332,6 +339,13 @@ class TdxClient: def get_minute_time_data(self, market: Market, code: str) -> list[MinuteBar]: """获取今日分时数据(240条)。""" + today = _today_in_shanghai() + try: + bars = self.get_history_minute_time_data(market, code, today) + if bars: + return bars + except Exception: + pass return self._execute(GetMinuteTimeDataCmd(market, code)) def get_history_minute_time_data( @@ -742,6 +756,13 @@ class AsyncTdxClient: return await self._execute(GetIndexBarsCmd(market, code, category, start, count)) async def get_minute_time_data(self, market: Market, code: str) -> list[MinuteBar]: + today = _today_in_shanghai() + try: + bars = await self.get_history_minute_time_data(market, code, today) + if bars: + return bars + except Exception: + pass return await self._execute(GetMinuteTimeDataCmd(market, code)) async def get_history_minute_time_data( diff --git a/tests/unit/test_a_share_extensions.py b/tests/unit/test_a_share_extensions.py index 9b9dcd3..481ecbe 100644 --- a/tests/unit/test_a_share_extensions.py +++ b/tests/unit/test_a_share_extensions.py @@ -1,14 +1,16 @@ """针对本轮 A 股增强功能的单元测试。""" +import asyncio import struct from unittest.mock import patch -from xmtdx import Market, TdxClient +from xmtdx import AsyncTdxClient, Market, TdxClient from xmtdx.client import _classify_fund_flow from xmtdx.models.bar import SecurityBar from xmtdx.models.quote import SecurityQuote from xmtdx.models.security import SecurityInfo from xmtdx.models.stats import HistoricalFundFlow +from xmtdx.models.timeseries import MinuteBar from xmtdx.models.timeseries import TransactionRecord @@ -219,3 +221,70 @@ def test_get_price_limits_uses_listing_window(_mock_conn_cls): 11.0, 9.0, ) + + +@patch("xmtdx.client.TdxConnection") +def test_get_minute_time_data_prefers_history_endpoint(_mock_conn_cls): + """今日分时优先走历史分时接口,规避当前分时协议歧义。""" + client = TdxClient("127.0.0.1") + expected = [MinuteBar(price=9.7, vol=13694)] + + with patch("xmtdx.client._today_in_shanghai", return_value=20260422), patch.object( + TdxClient, + "get_history_minute_time_data", + return_value=expected, + ) as mock_history, patch.object( + TdxClient, + "_execute", + side_effect=AssertionError("should not hit current-minute command"), + ): + result = client.get_minute_time_data(Market.SH, "600000") + + mock_history.assert_called_once_with(Market.SH, "600000", 20260422) + assert result == expected + + +@patch("xmtdx.client.TdxConnection") +def test_get_minute_time_data_falls_back_to_current_endpoint(_mock_conn_cls): + """历史分时失败时,仍回退到原今日分时命令。""" + client = TdxClient("127.0.0.1") + fallback = [MinuteBar(price=9.61, vol=10698)] + + with patch("xmtdx.client._today_in_shanghai", return_value=20260422), patch.object( + TdxClient, + "get_history_minute_time_data", + side_effect=RuntimeError("history unavailable"), + ) as mock_history, patch.object( + TdxClient, + "_execute", + return_value=fallback, + ) as mock_execute: + result = client.get_minute_time_data(Market.SH, "600000") + + mock_history.assert_called_once_with(Market.SH, "600000", 20260422) + mock_execute.assert_called_once() + assert result == fallback + + +def test_async_get_minute_time_data_prefers_history_endpoint(): + """异步客户端应与同步客户端保持同一回退策略。""" + expected = [MinuteBar(price=9.7, vol=13694)] + + async def run_test() -> None: + with patch("xmtdx.client.AsyncTdxConnection"): + client = AsyncTdxClient("127.0.0.1") + with patch("xmtdx.client._today_in_shanghai", return_value=20260422), patch.object( + AsyncTdxClient, + "get_history_minute_time_data", + return_value=expected, + ) as mock_history, patch.object( + AsyncTdxClient, + "_execute", + side_effect=AssertionError("should not hit current-minute command"), + ): + result = await client.get_minute_time_data(Market.SH, "600000") + + mock_history.assert_called_once_with(Market.SH, "600000", 20260422) + assert result == expected + + asyncio.run(run_test())