Skip to content

Commit

Permalink
TST: generic calendar checker against fixing data (#660)
Browse files Browse the repository at this point in the history
Co-authored-by: JHM Darbyshire (win11) <[email protected]>
  • Loading branch information
attack68 and attack68 authored Jan 27, 2025
1 parent c99c3a9 commit fd77c1f
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 43 deletions.
2 changes: 1 addition & 1 deletion python/rateslib/rs.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class _DateRoll:
def to_json(self) -> str: ...

class Cal(_DateRoll):
def __init__(self, rules: list[datetime], weekmask: list[int]) -> None: ...
def __init__(self, rules: list[datetime], week_mask: list[int]) -> None: ...

class UnionCal(_DateRoll):
calendars: list[Cal] = ...
Expand Down
41 changes: 39 additions & 2 deletions python/tests/test_calendarsrs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from datetime import datetime as dt

import pytest
from pandas import Index
from rateslib import defaults
from rateslib.calendars import _get_modifier, get_calendar
from rateslib.json import from_json
from rateslib.rs import Cal, Modifier, NamedCal, RollDay, UnionCal
Expand Down Expand Up @@ -44,8 +46,10 @@ def test_cal_construct(self) -> None:
UnionCal([cal], None)

def test_is_business_day(self, simple_cal, simple_union) -> None:
assert not simple_cal.is_bus_day(dt(2015, 9, 7))
assert simple_cal.is_bus_day(dt(2015, 9, 8))
assert not simple_cal.is_bus_day(dt(2015, 9, 7)) # Monday Holiday
assert simple_cal.is_bus_day(dt(2015, 9, 8)) # Tuesday
assert not simple_cal.is_bus_day(dt(2015, 9, 12)) # Saturday

assert not simple_union.is_bus_day(dt(2015, 9, 7))
assert simple_union.is_bus_day(dt(2015, 9, 8))

Expand Down Expand Up @@ -212,3 +216,36 @@ def test_equality_named_cal(self) -> None:
ncal = NamedCal("ldn,tgt|fed")
assert ucal == ncal
assert ncal == ucal


@pytest.mark.parametrize(
("datafile", "calendar", "known_exceptions"),
[
("usd_rfr", "nyc", []),
("gbp_rfr", "ldn", [dt(2020, 5, 4)]),
("cad_rfr", "tro", []),
("eur_rfr", "tgt", []),
("jpy_rfr", "tyo", []),
("sek_rfr", "stk", []),
("nok_rfr", "osl", []),
],
)
def test_calendar_against_historical_fixings(datafile, calendar, known_exceptions):
fixings = defaults.fixings[datafile]
calendar_ = get_calendar(calendar)
bus_days = Index(calendar_.bus_date_range(fixings.index[0], fixings.index[-1]))
diff = fixings.index.difference(bus_days)

errors = 0
if len(diff) != 0:
print(f"{calendar} for {datafile}")
for date in diff:
if date in known_exceptions:
continue
elif date in fixings.index:
print(f"{date} exists in fixings: does calendar wrongly classify as a holiday?")
else:
print(f"{date} exists in calendar: should this date be classified as a holdiay?")
errors += 1

assert errors == 0
59 changes: 59 additions & 0 deletions python/tests/test_periods.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pandas import NA, DataFrame, Index, MultiIndex, Series, date_range
from pandas.testing import assert_frame_equal
from rateslib import defaults
from rateslib.calendars import Cal
from rateslib.curves import CompositeCurve, Curve, IndexCurve, LineCurve
from rateslib.default import NoInput
from rateslib.dual import Dual
Expand Down Expand Up @@ -1145,6 +1146,64 @@ def test_period_historic_fixings_series_missing_warns(
):
period.rate(curve)

def test_more_fixings_than_expected_by_calednar_raises(self):
# Create historical fixings spanning 5 days for a FloatPeriod.
# But set a Cal that does not expect all of these - one holdiay midweek.
# Observe the rate calculation.
fixings = Series(
data=[1.0, 2.0, 3.0, 4.0, 5.0],
index=[
dt(2023, 1, 23),
dt(2023, 1, 24),
dt(2023, 1, 25),
dt(2023, 1, 26),
dt(2023, 1, 27),
],
)
cal = Cal(holidays=[dt(2023, 1, 25)], week_mask=[5, 6])
period = FloatPeriod(
start=dt(2023, 1, 23),
end=dt(2023, 1, 30),
payment=dt(2023, 1, 30),
frequency="Q",
fixing_method="rfr_payment_delay",
fixings=fixings,
convention="act365F",
calendar="bus",
)
curve = Curve({dt(2023, 1, 26): 1.0, dt(2025, 1, 26): 1.0}, calendar=cal)
msg = "The supplied `fixings` contain more fixings than were expected"
with pytest.raises(ValueError, match=msg):
period.rate(curve)

def test_fewer_fixings_than_expected_by_calendar_warns_and_fills(self):
# Create historical fixings spanning 4 days for a FloatPeriod, with mid-week holiday
# But set a Cal that expects 5 (the cal does not have the holiday)
# Observe the rate calculation.

# this tests performs a minimal version of test_period_historic_fixings_series_missing_warns
fixings = Series(
data=[1.0, 2.0, 4.0, 5.0],
index=[dt(2023, 1, 23), dt(2023, 1, 24), dt(2023, 1, 26), dt(2023, 1, 27)],
)
cal = Cal(holidays=[], week_mask=[5, 6])
period = FloatPeriod(
start=dt(2023, 1, 23),
end=dt(2023, 1, 30),
payment=dt(2023, 1, 30),
frequency="Q",
fixing_method="rfr_payment_delay",
fixings=fixings,
convention="act365F",
calendar="bus",
)
curve = Curve({dt(2023, 1, 29): 1.0, dt(2025, 1, 22): 1.0}, calendar=cal)
with (
pytest.raises(ValueError, match="RFRs could not be calculated, have you missed"),
pytest.warns(UserWarning, match="`fixings` has missed a calendar value"),
):
period.rate(curve)

def test_fixing_with_float_spread_warning(self, curve) -> None:
float_period = FloatPeriod(
start=dt(2022, 1, 4),
Expand Down
Loading

0 comments on commit fd77c1f

Please sign in to comment.