galaxis-po/backend/tests/unit/test_walkforward.py

116 lines
3.8 KiB
Python
Raw Normal View History

"""
Unit tests for WalkForwardEngine window generation logic.
"""
from datetime import date
import pytest
from app.services.backtest.walkforward_engine import WalkForwardEngine, Window
class TestGenerateWindows:
"""Test _generate_windows static method."""
def test_basic_windows(self):
"""2-year period with 12m train, 3m test, 3m step -> 4 windows."""
windows = WalkForwardEngine._generate_windows(
start=date(2020, 1, 1),
end=date(2021, 12, 31),
train_months=12,
test_months=3,
step_months=3,
)
assert len(windows) == 4
assert windows[0].index == 0
assert windows[0].train_start == date(2020, 1, 1)
assert windows[0].train_end == date(2020, 12, 31)
assert windows[0].test_start == date(2021, 1, 1)
assert windows[0].test_end == date(2021, 3, 31)
def test_single_window(self):
"""Exactly 15 months -> 1 window with 12m train + 3m test."""
windows = WalkForwardEngine._generate_windows(
start=date(2020, 1, 1),
end=date(2021, 3, 31),
train_months=12,
test_months=3,
step_months=3,
)
assert len(windows) == 1
assert windows[0].train_start == date(2020, 1, 1)
assert windows[0].test_end == date(2021, 3, 31)
def test_no_windows_period_too_short(self):
"""Period shorter than train + test -> 0 windows."""
windows = WalkForwardEngine._generate_windows(
start=date(2020, 1, 1),
end=date(2020, 12, 31),
train_months=12,
test_months=3,
step_months=3,
)
assert len(windows) == 0
def test_partial_last_window(self):
"""Last window with partial test period is included."""
windows = WalkForwardEngine._generate_windows(
start=date(2020, 1, 1),
end=date(2021, 2, 15),
train_months=12,
test_months=3,
step_months=3,
)
assert len(windows) == 1
assert windows[0].test_end == date(2021, 2, 15)
def test_step_larger_than_test(self):
"""step_months > test_months creates non-overlapping test windows."""
windows = WalkForwardEngine._generate_windows(
start=date(2019, 1, 1),
end=date(2022, 12, 31),
train_months=12,
test_months=3,
step_months=6,
)
assert len(windows) >= 2
# test windows should not overlap
for i in range(1, len(windows)):
assert windows[i].test_start > windows[i - 1].test_end
def test_monthly_step(self):
"""step_months=1 creates many overlapping windows."""
windows = WalkForwardEngine._generate_windows(
start=date(2020, 1, 1),
end=date(2021, 6, 30),
train_months=6,
test_months=3,
step_months=1,
)
assert len(windows) >= 9
def test_window_indices_sequential(self):
"""Window indices should be sequential starting from 0."""
windows = WalkForwardEngine._generate_windows(
start=date(2019, 1, 1),
end=date(2022, 12, 31),
train_months=12,
test_months=3,
step_months=3,
)
for i, w in enumerate(windows):
assert w.index == i
def test_window_dates_consistent(self):
"""train_end < test_start and test_start <= test_end for all windows."""
windows = WalkForwardEngine._generate_windows(
start=date(2019, 1, 1),
end=date(2023, 12, 31),
train_months=12,
test_months=6,
step_months=3,
)
for w in windows:
assert w.train_start < w.train_end
assert w.train_end < w.test_start
assert w.test_start <= w.test_end