From 9759396ef5b48a9f9560e6a5638ee30106effbf0 Mon Sep 17 00:00:00 2001 From: parlorsky <69785001+parlorsky@users.noreply.github.com> Date: Wed, 7 Oct 2026 21:25:22 +0300 Subject: [PATCH 1/2] fix(calendar): retain periods in sparse calendar resampling --- qlib/utils/resam.py | 8 +++--- tests/misc/test_resam.py | 56 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 tests/misc/test_resam.py diff --git a/qlib/utils/resam.py b/qlib/utils/resam.py index 99aedfcd50c..9240d57421d 100644 --- a/qlib/utils/resam.py +++ b/qlib/utils/resam.py @@ -57,13 +57,13 @@ def resam_calendar( return _calendar_day[:: freq_sam.count] elif freq_sam.base == Freq.NORM_FREQ_WEEK: - _day_in_week = np.array(list(map(lambda x: x.dayofweek, _calendar_day))) - _calendar_week = _calendar_day[np.ediff1d(_day_in_week, to_begin=-1) < 0] + _week = pd.DatetimeIndex(_calendar_day).to_period("W") + _calendar_week = _calendar_day[~_week.duplicated()] return _calendar_week[:: freq_sam.count] elif freq_sam.base == Freq.NORM_FREQ_MONTH: - _day_in_month = np.array(list(map(lambda x: x.day, _calendar_day))) - _calendar_month = _calendar_day[np.ediff1d(_day_in_month, to_begin=-1) < 0] + _month = pd.DatetimeIndex(_calendar_day).to_period("M") + _calendar_month = _calendar_day[~_month.duplicated()] return _calendar_month[:: freq_sam.count] else: raise ValueError("sampling freq must be xmin, xd, xw, xm") diff --git a/tests/misc/test_resam.py b/tests/misc/test_resam.py new file mode 100644 index 00000000000..1b80c62601d --- /dev/null +++ b/tests/misc/test_resam.py @@ -0,0 +1,56 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import numpy as np +import pandas as pd +import pytest + +from qlib.constant import REG_CN +from qlib.utils.resam import resam_calendar + + +@pytest.mark.parametrize( + "dates, raw_freq, sampled_freq, expected", + [ + ( + ["2024-01-01", "2024-01-02", "2024-01-09", "2024-01-10", "2024-01-17"], + "day", + "week", + ["2024-01-01", "2024-01-09", "2024-01-17"], + ), + ( + ["2023-12-25", "2024-01-01", "2024-01-08", "2024-01-15"], + "week", + "2week", + ["2023-12-25", "2024-01-08"], + ), + ( + ["2023-12-04", "2023-12-05", "2024-01-08", "2024-01-09", "2024-02-12"], + "day", + "month", + ["2023-12-04", "2024-01-08", "2024-02-12"], + ), + ( + ["2023-12-01", "2024-01-01", "2024-02-01", "2024-03-01"], + "month", + "2month", + ["2023-12-01", "2024-02-01"], + ), + ( + ["2023-12-29 09:30", "2023-12-29 13:00", "2024-01-02 09:30", "2024-01-02 13:00"], + "1min", + "week", + ["2023-12-29", "2024-01-02"], + ), + ], +) +def test_resam_calendar_period_boundaries(dates, raw_freq, sampled_freq, expected): + calendar = np.array([pd.Timestamp(value) for value in dates]) + result = resam_calendar(calendar, raw_freq, sampled_freq, region=REG_CN) + np.testing.assert_array_equal(result, np.array([pd.Timestamp(value) for value in expected])) + + +@pytest.mark.parametrize("sampled_freq", ["week", "month", "2week", "2month"]) +def test_resam_calendar_empty(sampled_freq): + calendar = np.array([], dtype=object) + np.testing.assert_array_equal(resam_calendar(calendar, "day", sampled_freq, region=REG_CN), calendar) From c79bcf4553bd81f9f028e5cd452a80c891a34dc3 Mon Sep 17 00:00:00 2001 From: parlorsky <69785001+parlorsky@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:37:05 +0300 Subject: [PATCH 2/2] test(calendar): document sampling across empty periods --- qlib/utils/resam.py | 2 ++ tests/misc/test_resam.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/qlib/utils/resam.py b/qlib/utils/resam.py index 9240d57421d..01c082da650 100644 --- a/qlib/utils/resam.py +++ b/qlib/utils/resam.py @@ -25,6 +25,8 @@ def resam_calendar( Frequency of the raw calendar freq_sam : str Sample frequency + Weekly and monthly sampling counts periods present in the input, so entirely + missing weeks or months do not advance the sampling count. region: str Region, for example, "cn", "us" Returns diff --git a/tests/misc/test_resam.py b/tests/misc/test_resam.py index 1b80c62601d..0883a9dc279 100644 --- a/tests/misc/test_resam.py +++ b/tests/misc/test_resam.py @@ -50,6 +50,34 @@ def test_resam_calendar_period_boundaries(dates, raw_freq, sampled_freq, expecte np.testing.assert_array_equal(result, np.array([pd.Timestamp(value) for value in expected])) +@pytest.mark.parametrize( + "start, end, gap_start, gap_end, sampled_freq, expected", + [ + ( + "2024-01-29", + "2024-03-08", + "2024-02-12", + "2024-02-16", + "2week", + ["2024-01-29", "2024-02-19", "2024-03-04"], + ), + ( + "2024-01-01", + "2024-06-28", + "2024-03-01", + "2024-03-31", + "2month", + ["2024-01-01", "2024-04-01", "2024-06-03"], + ), + ], +) +def test_resam_calendar_counts_nonempty_periods(start, end, gap_start, gap_end, sampled_freq, expected): + calendar = pd.bdate_range(start, end) + calendar = calendar[(calendar < gap_start) | (calendar > gap_end)] + result = resam_calendar(calendar.to_pydatetime(), "day", sampled_freq, region=REG_CN) + np.testing.assert_array_equal(result, np.array([pd.Timestamp(value) for value in expected])) + + @pytest.mark.parametrize("sampled_freq", ["week", "month", "2week", "2month"]) def test_resam_calendar_empty(sampled_freq): calendar = np.array([], dtype=object)