diff --git a/pandas_market_calendars/calendar_registry.py b/pandas_market_calendars/calendar_registry.py index 81a385ab..ded8e70e 100644 --- a/pandas_market_calendars/calendar_registry.py +++ b/pandas_market_calendars/calendar_registry.py @@ -14,6 +14,7 @@ from .calendars.cme_globex_fx import CMEGlobexFXExchangeCalendar from .calendars.cme_globex_fixed_income import CMEGlobexFixedIncomeCalendar from .calendars.eurex import EUREXExchangeCalendar +from .calendars.eurex_fixed_income import EUREXFixedIncomeCalendar from .calendars.hkex import HKEXExchangeCalendar from .calendars.ice import ICEExchangeCalendar from .calendars.iex import IEXExchangeCalendar diff --git a/pandas_market_calendars/calendars/eurex_fixed_income.py b/pandas_market_calendars/calendars/eurex_fixed_income.py new file mode 100644 index 00000000..20b7443a --- /dev/null +++ b/pandas_market_calendars/calendars/eurex_fixed_income.py @@ -0,0 +1,99 @@ +from .cme_globex_base import CMEGlobexBaseExchangeCalendar + +from datetime import time + +from pandas.tseries.holiday import ( + AbstractHolidayCalendar, + EasterMonday, + GoodFriday, + Holiday, +) +from pytz import timezone + +from pandas_market_calendars.market_calendar import ( + FRIDAY, + MONDAY, + MarketCalendar, + THURSDAY, + TUESDAY, + WEDNESDAY, +) + + +# New Year's Day +EUREXNewYearsDay = Holiday( + "New Year's Day", + month=1, + day=1, + days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), +) +# Early May bank holiday +MayBank = Holiday( + "Early May Bank Holiday", + month=5, + day=1, + days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), +) +# Christmas Eve +ChristmasEve = Holiday( + "Christmas Eve", + month=12, + day=24, + days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), +) +# Christmas +Christmas = Holiday( + "Christmas", + month=12, + day=25, + days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), +) +# Boxing day +BoxingDay = Holiday( + "Boxing Day", + month=12, + day=26, + days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), +) +# New Year's Eve +NewYearsEve = Holiday( + "New Year's Eve", + month=12, + day=31, + days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), +) + +class EUREXFixedIncomeCalendar(MarketCalendar): + """ + Trading calendar available here: + https://www.eurex.com/resource/blob/3378814/910cf372738890f691bc1bfbccfd3aef/data/tradingcalendar_2023_en.pdf + """ + aliases = ["EUREX Fixed Income"] + + regular_market_times = { + "market_open": ((None, time(1, 10)), ("2018-12-10", time(8, 0))), + "market_close": ((None, time(22)),), + } + + @property + def name(self): + return "EUREX Fixed Income" + + @property + def tz(self): + return timezone("Europe/Berlin") + + @property + def regular_holidays(self): + return AbstractHolidayCalendar( + rules=[ + EUREXNewYearsDay, + GoodFriday, + EasterMonday, + MayBank, + ChristmasEve, + Christmas, + BoxingDay, + NewYearsEve, + ] + ) diff --git a/tests/test_eurex_fixed_income_calendar.py b/tests/test_eurex_fixed_income_calendar.py new file mode 100644 index 00000000..da239277 --- /dev/null +++ b/tests/test_eurex_fixed_income_calendar.py @@ -0,0 +1,95 @@ +import pandas as pd +import pytz + +from pandas_market_calendars.calendars.eurex_fixed_income import EUREXFixedIncomeCalendar + + +def test_time_zone(): + assert EUREXFixedIncomeCalendar().tz == pytz.timezone("Europe/Berlin") + assert EUREXFixedIncomeCalendar().name == "EUREX Fixed Income" + +def _test_year_holiday(year, bad_dates): + eurex = EUREXFixedIncomeCalendar() + good_dates = eurex.valid_days(f"{year}-01-01", f"{year}-12-31") + + # Make sure holiday dates aren't in the schedule + for date in bad_dates: + assert pd.Timestamp(date, tz="UTC") not in good_dates + + # Make sure all other weekdays are in the schedule + expected_good_dates = [ + d.strftime("%Y-%m-%d") + for d in pd.date_range(f"{year}-01-01", f"{year}-12-31", freq="D") + if d.weekday() < 5 and d.strftime("%Y-%m-%d") not in bad_dates + ] + for date in expected_good_dates: + assert pd.Timestamp(date, tz="UTC") in good_dates + +def test_2017_holidays(): + """ + Eurex is closed for trading and clearing (exercise, settlement and cash) + in all derivatives: 14 April, 17 April, 1 May, 25 December, 26 December + """ + bad_dates = ["2017-04-14", "2017-04-17", "2017-05-01", "2017-12-25", "2017-12-26"] + _test_year_holiday(2017, bad_dates) + +def test_2018_holidays(): + """ + Eurex is closed for trading and clearing (exercise, settlement and cash) + in all derivatives: 1 January, 30 March, 2 April, 1 May, 25 December, 26 December + Eurex is closed for trading in all derivatives: 24 December, 31 December + """ + bad_dates = ["2018-01-01", "2018-03-30", "2018-04-02", "2018-05-01", "2018-12-24", "2018-12-25", "2018-12-26", + "2018-12-31"] + _test_year_holiday(2018, bad_dates) + +def test_2019_holidays(): + """ + Eurex is closed for trading and clearing (exercise, settlement and cash) + in all derivatives: 1 January, 19 April, 22 April, 1 May, 25 December, 26 December + Eurex is closed for trading in all derivatives: 24 December, 31 December + """ + bad_dates = ["2019-01-01", "2019-04-19", "2019-04-22", "2019-05-01", "2019-12-24", "2019-12-25", "2019-12-26", + "2019-12-31"] + _test_year_holiday(2019, bad_dates) + +def test_2020_holidays(): + """ + Eurex is closed for trading and clearing (exercise, settlement and cash) + in all derivatives: 1 January, 10 April, 13 April, 1 May, 25 December + Eurex is closed for trading in all derivatives: 24 December, 31 December + """ + bad_dates = ["2020-01-01", "2020-04-10", "2020-04-13", "2020-05-01", "2020-12-24", "2020-12-25", "2020-12-31"] + _test_year_holiday(2020, bad_dates) + +def test_2021_holidays(): + """ + Eurex is closed for trading and clearing (exercise, settlement and cash) + in all derivatives: 1 January, 2 April, 5 April + Eurex is closed for trading in all derivatives: 24 December, 31 December + """ + bad_dates = ["2021-01-01", "2021-04-02", "2021-04-05", "2021-05-01", "2021-12-24", "2021-12-31"] + _test_year_holiday(2021, bad_dates) + + +def test_2022_holidays(): + """ + Eurex is closed for trading and clearing (exercise, settlement and cash) + in all derivatives: 15 April, 18 April, 26 December + """ + bad_dates = ["2022-04-15", "2022-04-18", "2022-12-26"] + _test_year_holiday(2022, bad_dates) + + +def test_2023_holidays(): + """ + Eurex is closed for trading and clearing (exercise, settlement and cash) + in all derivatives: 7 April, 10 April, 1 May, 25 December, 26 December + """ + bad_dates = ["2023-04-07", "2023-04-10", "2023-05-01", "2023-12-25", "2023-12-26"] + _test_year_holiday(2023, bad_dates) + + +def test_2024_holidays(): + bad_dates = ["2024-01-01", "2024-03-29", "2024-04-01", "2024-05-01", "2024-12-24", "2024-12-25", "2024-12-26", "2024-12-31"] + _test_year_holiday(2024, bad_dates) \ No newline at end of file