mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 15:44:15 +08:00
通达信协议用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
111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
"""日期时间解码单元测试。"""
|
||
|
||
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
|