Files
easy_tdx_max/tests/unit/test_codec_datetime.py
T
GitHub 615994ad72 feat(kline): 分钟级K线时间戳可选bar_time对齐Tushare (Discussion #7)
通达信协议用bar开始时间打时间戳(5min线上午最后一根标11:25、下午第一根标13:00;午休11:30-13:00无bar),而Tushare/同花顺/聚宽用bar结束时间(标11:30/13:05)。新增bar_time参数让用户一键切换,避免自行+5分钟偏移。

- 全部3条K线路径覆盖:A股get_security_bars/get_index_bars、扩展行情get_instrument_bars、MAC get_stock_kline(含同步+异步、get_stock_kline_with_indicators)

- CLI kline新增--bar-time {start,end}选项;Web /bars、/bars/index新增bar_time查询参数

- bar_time=start(默认)保持完全向后兼容;bar_time=end仅对分钟级周期(1/5/15/30/60min)生效,自动按周期时长右移并处理跨小时/跨日边界

- 协议解码层零改动,偏移作为纯展示语义在client层后处理,单一工具函数_apply_bar_time_align_df/_apply_bar_time_align_bars复用于全部路径

- 新增27个单元测试(test_codec_datetime.py偏移逻辑 + test_kline_bar_time.py三路径覆盖),全量700单测通过

- bump 版本号至 1.16.0
2026-06-30 15:02:27 +08:00

111 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""日期时间解码单元测试。"""
import struct
from easy_tdx.codec.datetime_ import get_datetime, get_datetime_day, get_datetime_minute, get_time
def _pack_minute(year: int, month: int, day: int, hour: int, minute: int) -> bytes:
zipday = ((year - 2004) << 11) | (month * 100 + day)
tminutes = hour * 60 + minute
return struct.pack("<HH", zipday, tminutes)
def _pack_day(year: int, month: int, day: int) -> bytes:
return struct.pack("<I", year * 10000 + month * 100 + day)
class TestGetDatetimeMinute:
def test_basic(self):
data = _pack_minute(2024, 4, 10, 14, 30)
y, mo, d, h, mi, pos = get_datetime_minute(data, 0)
assert (y, mo, d, h, mi) == (2024, 4, 10, 14, 30)
assert pos == 4
def test_open_time(self):
data = _pack_minute(2026, 1, 5, 9, 30)
y, mo, d, h, mi, pos = get_datetime_minute(data, 0)
assert h == 9 and mi == 30
def test_close_time(self):
data = _pack_minute(2026, 1, 5, 15, 0)
y, mo, d, h, mi, _ = get_datetime_minute(data, 0)
assert h == 15 and mi == 0
class TestGetDatetimeDay:
def test_basic(self):
data = _pack_day(2026, 4, 10)
y, mo, d, pos = get_datetime_day(data, 0)
assert (y, mo, d) == (2026, 4, 10)
assert pos == 4
class TestGetDatetime:
def test_minute_category(self):
data = _pack_minute(2026, 3, 15, 10, 0)
for cat in (0, 1, 2, 3, 7, 8):
y, mo, d, h, mi, _ = get_datetime(cat, data, 0)
assert h == 10 and mi == 0
def test_day_category(self):
data = _pack_day(2026, 3, 15)
for cat in (4, 5, 6, 9):
y, mo, d, h, mi, _ = get_datetime(cat, data, 0)
assert (y, mo, d) == (2026, 3, 15)
assert h == 15 and mi == 0
class TestGetTime:
def test_basic(self):
data = struct.pack("<H", 14 * 60 + 30) # 14:30
h, mi, pos = get_time(data, 0)
assert h == 14 and mi == 30
assert pos == 2
class TestCategoryToMinutes:
"""分钟级 KlineCategory → 每根 bar 的分钟数;日线及以上返回 None。"""
def test_minute_categories(self):
from easy_tdx._df import _category_to_minutes
# MIN_5/15/30/60/1/3
assert _category_to_minutes(0) == 5
assert _category_to_minutes(1) == 15
assert _category_to_minutes(2) == 30
assert _category_to_minutes(3) == 60
assert _category_to_minutes(7) == 1
assert _category_to_minutes(8) == 3
def test_daily_plus_returns_none(self):
from easy_tdx._df import _category_to_minutes
for cat in (4, 5, 6, 9, 10, 11): # DAY/WEEK/MONTH/YEAR/SEASON/YEAR_ALT
assert _category_to_minutes(cat) is None
class TestPeriodToMinutes:
"""MAC 协议 Period → 每根 bar 的分钟数。"""
def test_basic_periods(self):
from easy_tdx._df import _period_to_minutes
assert _period_to_minutes(0) == 5 # MIN_5
assert _period_to_minutes(1) == 15 # MIN_15
assert _period_to_minutes(2) == 30 # MIN_30
assert _period_to_minutes(3) == 60 # MIN_60
assert _period_to_minutes(7) == 1 # MIN_1
def test_mins_multiplied_by_times(self):
from easy_tdx._df import _period_to_minutes
assert _period_to_minutes(8, 1) == 5 # MINS ×1
assert _period_to_minutes(8, 3) == 15 # MINS ×3 = 15 分钟线
def test_daily_plus_and_seconds_return_none(self):
from easy_tdx._df import _period_to_minutes
for p in (4, 5, 6, 9, 10, 11, 13): # DAILY/WEEKLY/MONTHLY/DAYS/QUARTERLY/YEARLY/SECONDS
assert _period_to_minutes(p) is None