You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

95 lines
3.3 KiB
Python

"""Unit tests for libs/backtest/calendar.py."""
from __future__ import annotations
import datetime as dt
import pytest
from libs.backtest.calendar import (
get_trading_days,
is_trading_day,
next_trading_day,
resolve_execution_date,
)
class TestResolvExecutionDate:
def test_pre_market_weekday(self):
# pre_market on a trading day: reaction = same day, execution = next trading day
event_date = dt.date(2026, 1, 5) # Monday
exec_date = resolve_execution_date(event_date, "pre_market")
assert is_trading_day(exec_date)
assert exec_date > event_date
def test_post_market_weekday(self):
# post_market: reaction = next trading day, execution = trading day after that
event_date = dt.date(2026, 1, 5) # Monday
exec_date = resolve_execution_date(event_date, "post_market")
assert is_trading_day(exec_date)
assert exec_date > event_date
def test_post_market_friday(self):
# post_market Friday → reaction = Monday, execution = Tuesday
friday = dt.date(2026, 1, 2) # Friday
exec_date = resolve_execution_date(friday, "post_market")
assert is_trading_day(exec_date)
# Must be at least Monday
assert exec_date >= dt.date(2026, 1, 5)
def test_unknown_bucket(self):
# unknown treated same as post_market
event_date = dt.date(2026, 1, 5)
exec_date = resolve_execution_date(event_date, "unknown")
assert is_trading_day(exec_date)
assert exec_date > event_date
def test_execution_after_reaction(self):
"""execution_date should always be strictly after event_date."""
for bucket in ["pre_market", "regular_hours", "post_market", "unknown"]:
exec_date = resolve_execution_date(dt.date(2026, 1, 5), bucket)
assert exec_date > dt.date(2026, 1, 5), f"Failed for bucket: {bucket}"
class TestGetTradingDays:
def test_basic_range(self):
days = get_trading_days(dt.date(2026, 1, 5), dt.date(2026, 1, 9))
assert len(days) == 5 # Mon-Fri
assert all(is_trading_day(d) for d in days)
def test_excludes_weekends(self):
days = get_trading_days(dt.date(2026, 1, 3), dt.date(2026, 1, 11))
for d in days:
assert d.weekday() < 5 # Not Saturday (5) or Sunday (6)
def test_single_day(self):
days = get_trading_days(dt.date(2026, 1, 5), dt.date(2026, 1, 5))
assert len(days) == 1
assert days[0] == dt.date(2026, 1, 5)
def test_sorted_ascending(self):
days = get_trading_days(dt.date(2026, 1, 5), dt.date(2026, 1, 30))
assert days == sorted(days)
class TestIsTradingDay:
def test_weekday_is_trading(self):
assert is_trading_day(dt.date(2026, 1, 5)) # Monday
def test_weekend_not_trading(self):
assert not is_trading_day(dt.date(2026, 1, 3)) # Saturday
def test_sunday_not_trading(self):
assert not is_trading_day(dt.date(2026, 1, 4)) # Sunday
class TestNextTradingDay:
def test_friday_to_monday(self):
friday = dt.date(2026, 1, 2)
nxt = next_trading_day(friday)
assert nxt == dt.date(2026, 1, 5) # Monday
def test_monday_to_tuesday(self):
monday = dt.date(2026, 1, 5)
nxt = next_trading_day(monday)
assert nxt == dt.date(2026, 1, 6)