# # Copyright 2019 Quantopian, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime from itertools import chain from zoneinfo import ZoneInfo import pandas as pd from pandas.tseries.holiday import MO, Easter, GoodFriday, Holiday from pandas.tseries.offsets import Day from .common_holidays import ( christmas, christmas_eve, european_labour_day, immaculate_conception, maundy_thursday, new_years_day, ) from .exchange_calendar import ( MONDAY, TUESDAY, WEDNESDAY, WEEKDAYS, HolidayCalendar, ExchangeCalendar, ) def nearest_monday(dt: datetime.datetime) -> datetime.datetime: """ If a holiday falls on Tuesday or Wednesday, it is observed on the previous Monday. If it falls on Thursday, Friday or a weekend, it is observed on the following Monday. """ day = dt.weekday() if day in (MONDAY, TUESDAY, WEDNESDAY): return dt - datetime.timedelta(day) while dt.weekday() != MONDAY: dt += datetime.timedelta(1) return dt def cultural_diversity_observance(dt: datetime.datetime) -> datetime.datetime | None: """ Apply the nearest monday rule, and also exclude 2012 (Day of Respect for Cultural Diversity breaks the nearest monday rule in 2012). """ hol = nearest_monday(dt) return hol if hol.year != 2012 else None def not_2018(dt: datetime.datetime) -> datetime.datetime | None: """ Exclude the year 2018 from the holiday list. """ return dt if dt.year != 2018 else None NewYearsDay = new_years_day() CarnivalMonday = Holiday( "Carnival Monday", month=1, day=1, start_date="2011", offset=[Easter(), -Day(48)], ) CarnivalTuesday = Holiday( "Carnival Tuesday", month=1, day=1, start_date="2011", offset=[Easter(), -Day(47)], ) TruthAndJusticeMemorialDay = Holiday( "Truth and Justice Memorial Day", month=3, day=24, start_date="2005", ) MalvinasDayTo2004 = Holiday( "Malvinas Day (up to 2004)", month=3, day=31, end_date="2005", offset=pd.DateOffset(weekday=MO(1)), ) MalvinasDayFrom2005 = Holiday( "Malvinas Day (2005-present)", month=4, day=2, start_date="2005" ) MaundyThursday = maundy_thursday() LabourDay = european_labour_day() MayDayRevolution = Holiday( "May Day Revolution", month=5, day=25, ) MartinMiguelDeGuemesDay = Holiday( "Martin Miguel de Guemes Day", month=6, day=17, start_date="2016", ) NationalFlagDayTo2010 = Holiday( "National Flag Day (up to 2010)", month=6, day=1, end_date="2011", offset=pd.DateOffset(weekday=MO(3)), ) NationalFlagDayFrom2011 = Holiday( "National Flag Day (2011-present)", month=6, day=20, start_date="2011", ) IndependenceDay = Holiday( "Independence Day", month=7, day=9, ) SanMartinsDayTo2010 = Holiday( "San Martin's Day", month=8, day=1, end_date="2011", offset=pd.DateOffset(weekday=MO(3)), ) SanMartinsDayFrom2012 = Holiday( "San Martin's Day", month=8, day=1, start_date="2012", offset=pd.DateOffset(weekday=MO(3)), ) DayOfRespectForCulturalDiversity = Holiday( "Day of Respect for Cultural Diversity", month=10, day=12, observance=cultural_diversity_observance, ) BankHoliday = Holiday( "Bank Holiday", month=11, day=6, end_date="2018", ) DayOfNationalSovereigntyTo2014 = Holiday( "Day of National Sovereignty (2010-2014)", month=11, day=1, start_date="2010", end_date="2015", offset=pd.DateOffset(weekday=MO(4)), ) DayOfNationalSovereigntyFrom2017 = Holiday( "Day of National Sovereignty (2017-present)", month=11, day=17, start_date="2017", offset=pd.DateOffset(weekday=MO(1)), ) ImmaculateConception = immaculate_conception() ChristmasEve = christmas_eve( days_of_week=WEEKDAYS, observance=not_2018, ) ChristmasDay = christmas() NewYearsEve = Holiday( "New Year's Eve", month=12, day=31, days_of_week=WEEKDAYS, observance=not_2018, ) class XBUEExchangeCalendar(ExchangeCalendar): """ Exchange calendar for the Buenos Aires Stock Exchange (XBUE). Open Time: 11:00 AM, AST Close Time: 5:00 PM, AST Regularly-Observed Holidays: - New Year's Day - Carnival Monday (48 days before Easter) - Carnival Tuesday (47 days before Easter) - Truth and Justice Memorial Day (Mar 24) - Malvinas Day (until 2004 first Monday after Mar 31, 2005-present Apr 2) - Good Friday - Labour Day - May Day Revolution (May 25) - Martin Miguel de Guemes Day (Jun 17, 2016-present) - National Flag Day (until 2010 3rd Monday in Jun, 2011-present Jun 20) - Independence Day (Jul 9) - San Martin's Day (3rd Monday in Aug) - Day of Respect for Cultural Diversity (2nd Monday in Oct) - Bank Holiday (Nov 6) - Day of National Sovereignty (4th Monday in Nov) - Immaculate Conception - Christmas Eve (2005-present) - Christmas Day - Last Trading Day of Year (2005-present) Early Closes: Note - we are unsure if these are early closes or holidays - Christmas Eve - New Year's Eve """ name = "XBUE" tz = ZoneInfo("America/Argentina/Buenos_Aires") open_times = ((None, datetime.time(11)),) close_times = ((None, datetime.time(17, 00)),) # TODO: Verify early close time regular_early_close = datetime.time(13) @property def regular_holidays(self): return HolidayCalendar( [ NewYearsDay, CarnivalMonday, CarnivalTuesday, TruthAndJusticeMemorialDay, MalvinasDayTo2004, MalvinasDayFrom2005, MaundyThursday, GoodFriday, LabourDay, MayDayRevolution, MartinMiguelDeGuemesDay, NationalFlagDayTo2010, NationalFlagDayFrom2011, IndependenceDay, SanMartinsDayTo2010, SanMartinsDayFrom2012, DayOfRespectForCulturalDiversity, BankHoliday, DayOfNationalSovereigntyTo2014, DayOfNationalSovereigntyFrom2017, ImmaculateConception, ChristmasDay, ] ) @property def adhoc_holidays(self): misc_adhocs = [ "2009-07-10", "2010-05-24", "2010-10-27", "2012-01-02", "2012-02-27", "2012-09-24", "2013-01-31", "2013-02-20", "2018-11-30", ] # There was a string of market closures in January, 2002 market_closures_2002_jan = [f"2002-01-{day:02d}" for day in range(7, 17)] # There was a string of market closures in April, 2002 market_closures_2002_apr = [f"2002-04-{day:02d}" for day in range(22, 27)] # There are occasionally "bridge days" that make long weekends when # a holiday falls on a Tuesday/Thursday, but these days are chosen # inconsistently. bridge_days = [ "2011-03-25", "2011-12-09", "2012-04-30", "2013-04-01", "2013-06-21", "2014-05-02", "2014-12-26", "2015-03-23", "2015-12-07", "2016-07-08", "2016-12-09", "2018-04-30", "2018-12-24", "2018-12-31", "2019-07-08", "2019-08-19", "2019-10-14", ] irregular_observances = [ "2012-10-08", # Cultural Diversity Day "2011-08-22", # San Martin's Day "2015-11-27", # Day of National Sovereignty "2016-11-28", # Day of National Sovereignty ] return [ pd.Timestamp(d) for d in chain( misc_adhocs, market_closures_2002_jan, market_closures_2002_apr, bridge_days, irregular_observances, ) ] @property def special_closes(self): return [ ( self.regular_early_close, HolidayCalendar( [ ChristmasEve, NewYearsEve, ] ), ), ]