pandas-market-calendars 4.1.3__py3-none-any.whl → 4.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. {pandas_market_calendars-4.1.3.dist-info → pandas_market_calendars-4.2.0.dist-info}/METADATA +200 -187
  2. pandas_market_calendars-4.2.0.dist-info/NOTICE +206 -0
  3. pandas_market_calendars-4.2.0.dist-info/RECORD +6 -0
  4. {pandas_market_calendars-4.1.3.dist-info → pandas_market_calendars-4.2.0.dist-info}/WHEEL +1 -1
  5. pandas_market_calendars/__init__.py +0 -37
  6. pandas_market_calendars/calendar_registry.py +0 -47
  7. pandas_market_calendars/calendar_utils.py +0 -225
  8. pandas_market_calendars/class_registry.py +0 -111
  9. pandas_market_calendars/exchange_calendar_asx.py +0 -63
  10. pandas_market_calendars/exchange_calendar_bmf.py +0 -227
  11. pandas_market_calendars/exchange_calendar_bse.py +0 -409
  12. pandas_market_calendars/exchange_calendar_cboe.py +0 -115
  13. pandas_market_calendars/exchange_calendar_cme.py +0 -240
  14. pandas_market_calendars/exchange_calendar_cme_globex_agriculture.py +0 -109
  15. pandas_market_calendars/exchange_calendar_cme_globex_base.py +0 -106
  16. pandas_market_calendars/exchange_calendar_cme_globex_energy_and_metals.py +0 -146
  17. pandas_market_calendars/exchange_calendar_cme_globex_equities.py +0 -104
  18. pandas_market_calendars/exchange_calendar_cme_globex_fixed_income.py +0 -114
  19. pandas_market_calendars/exchange_calendar_cme_globex_fx.py +0 -78
  20. pandas_market_calendars/exchange_calendar_eurex.py +0 -119
  21. pandas_market_calendars/exchange_calendar_hkex.py +0 -408
  22. pandas_market_calendars/exchange_calendar_ice.py +0 -65
  23. pandas_market_calendars/exchange_calendar_iex.py +0 -98
  24. pandas_market_calendars/exchange_calendar_jpx.py +0 -98
  25. pandas_market_calendars/exchange_calendar_lse.py +0 -91
  26. pandas_market_calendars/exchange_calendar_nyse.py +0 -1127
  27. pandas_market_calendars/exchange_calendar_ose.py +0 -150
  28. pandas_market_calendars/exchange_calendar_sifma.py +0 -300
  29. pandas_market_calendars/exchange_calendar_six.py +0 -114
  30. pandas_market_calendars/exchange_calendar_sse.py +0 -290
  31. pandas_market_calendars/exchange_calendar_tase.py +0 -119
  32. pandas_market_calendars/exchange_calendar_tsx.py +0 -159
  33. pandas_market_calendars/exchange_calendars_mirror.py +0 -114
  34. pandas_market_calendars/holidays_cme.py +0 -341
  35. pandas_market_calendars/holidays_cme_globex.py +0 -169
  36. pandas_market_calendars/holidays_cn.py +0 -1436
  37. pandas_market_calendars/holidays_jp.py +0 -362
  38. pandas_market_calendars/holidays_nyse.py +0 -1474
  39. pandas_market_calendars/holidays_oz.py +0 -65
  40. pandas_market_calendars/holidays_sifma.py +0 -321
  41. pandas_market_calendars/holidays_uk.py +0 -177
  42. pandas_market_calendars/holidays_us.py +0 -364
  43. pandas_market_calendars/jpx_equinox.py +0 -147
  44. pandas_market_calendars/market_calendar.py +0 -770
  45. pandas_market_calendars-4.1.3.dist-info/RECORD +0 -45
  46. {pandas_market_calendars-4.1.3.dist-info → pandas_market_calendars-4.2.0.dist-info}/LICENSE +0 -0
  47. {pandas_market_calendars-4.1.3.dist-info → pandas_market_calendars-4.2.0.dist-info}/top_level.txt +0 -0
@@ -1,37 +0,0 @@
1
- # Fork of Zipline by Quantopian released under MIT license. Original Zipline license below.
2
- #
3
- # Copyright 2016 Quantopian, Inc.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- import pkg_resources
18
-
19
- from .calendar_registry import get_calendar, get_calendar_names
20
- from .calendar_utils import convert_freq, date_range, merge_schedules
21
- # TODO: is the below needed? Can I replace all the imports on the calendars with ".market_calendar"
22
- from .market_calendar import MarketCalendar
23
-
24
- # if running in development there may not be a package
25
- try:
26
- __version__ = pkg_resources.get_distribution('pandas_market_calendars').version
27
- except pkg_resources.DistributionNotFound:
28
- __version__ = 'development'
29
-
30
- __all__ = [
31
- 'MarketCalendar',
32
- 'get_calendar',
33
- 'get_calendar_names',
34
- 'merge_schedules',
35
- 'date_range',
36
- 'convert_freq'
37
- ]
@@ -1,47 +0,0 @@
1
- from .market_calendar import MarketCalendar
2
- from .exchange_calendar_asx import ASXExchangeCalendar
3
- from .exchange_calendar_bmf import BMFExchangeCalendar
4
- from .exchange_calendar_cboe import CFEExchangeCalendar
5
- from .exchange_calendar_cme import \
6
- CMEEquityExchangeCalendar, \
7
- CMEBondExchangeCalendar
8
- from .exchange_calendar_cme_globex_base import CMEGlobexBaseExchangeCalendar
9
- from .exchange_calendar_cme_globex_agriculture import CMEGlobexAgricultureExchangeCalendar
10
- from .exchange_calendar_cme_globex_fx import CMEGlobexFXExchangeCalendar
11
- from .exchange_calendar_cme_globex_energy_and_metals import CMEGlobexEnergyAndMetalsExchangeCalendar
12
- from .exchange_calendar_cme_globex_equities import CMEGlobexEquitiesExchangeCalendar
13
- from .exchange_calendar_cme_globex_fixed_income import CMEGlobexFixedIncomeCalendar
14
- from .exchange_calendar_eurex import EUREXExchangeCalendar
15
- from .exchange_calendar_hkex import HKEXExchangeCalendar
16
- from .exchange_calendar_ice import ICEExchangeCalendar
17
- from .exchange_calendar_iex import IEXExchangeCalendar
18
- from .exchange_calendar_jpx import JPXExchangeCalendar
19
- from .exchange_calendar_lse import LSEExchangeCalendar
20
- from .exchange_calendar_nyse import NYSEExchangeCalendar
21
- from .exchange_calendar_ose import OSEExchangeCalendar
22
- from .exchange_calendar_sifma import SIFMAUSExchangeCalendar, SIFMAUKExchangeCalendar, SIFMAJPExchangeCalendar
23
- from .exchange_calendar_six import SIXExchangeCalendar
24
- from .exchange_calendar_sse import SSEExchangeCalendar
25
- from .exchange_calendar_tsx import TSXExchangeCalendar
26
- from .exchange_calendar_bse import BSEExchangeCalendar
27
- from .exchange_calendar_tase import TASEExchangeCalendar
28
- from .exchange_calendars_mirror import *
29
-
30
-
31
- def get_calendar(name, open_time=None, close_time=None):
32
- """
33
- Retrieves an instance of an MarketCalendar whose name is given.
34
-
35
- :param name: The name of the MarketCalendar to be retrieved.
36
- :param open_time: Market open time override as datetime.time object. If None then default is used.
37
- :param close_time: Market close time override as datetime.time object. If None then default is used.
38
- :return: MarketCalendar of the desired calendar.
39
- """
40
- return MarketCalendar.factory(name, open_time=open_time, close_time=close_time)
41
-
42
-
43
- def get_calendar_names():
44
- """All Market Calendar names and aliases that can be used in "factory"
45
- :return: list(str)
46
- """
47
- return MarketCalendar.calendar_names()
@@ -1,225 +0,0 @@
1
- """
2
- Utilities to use with market_calendars
3
- """
4
- import itertools
5
- import warnings
6
-
7
- import pandas as pd
8
- import numpy as np
9
-
10
- def merge_schedules(schedules, how='outer'):
11
- """
12
- Given a list of schedules will return a merged schedule. The merge method (how) will either return the superset
13
- of any datetime when any schedule is open (outer) or only the datetime where all markets are open (inner)
14
-
15
- CAVEATS:
16
- * This does not work for schedules with breaks, the break information will be lost.
17
- * Onlu "market_open" and "market_close" are considered, other market times are not yet supported.
18
-
19
- :param schedules: list of schedules
20
- :param how: outer or inner
21
- :return: schedule DataFrame
22
- """
23
- all_cols = [x.columns for x in schedules]
24
- all_cols = list(itertools.chain(*all_cols))
25
- if ('break_start' in all_cols) or ('break_end' in all_cols):
26
- warnings.warn('Merge schedules will drop the break_start and break_end from result.')
27
-
28
- result = schedules[0]
29
- for schedule in schedules[1:]:
30
- result = result.merge(schedule, how=how, right_index=True, left_index=True)
31
- if how == 'outer':
32
- result['market_open'] = result.apply(lambda x: min(x.market_open_x, x.market_open_y), axis=1)
33
- result['market_close'] = result.apply(lambda x: max(x.market_close_x, x.market_close_y), axis=1)
34
- elif how == 'inner':
35
- result['market_open'] = result.apply(lambda x: max(x.market_open_x, x.market_open_y), axis=1)
36
- result['market_close'] = result.apply(lambda x: min(x.market_close_x, x.market_close_y), axis=1)
37
- else:
38
- raise ValueError('how argument must be "inner" or "outer"')
39
- result = result[['market_open', 'market_close']]
40
- return result
41
-
42
-
43
- def convert_freq(index, frequency):
44
- """
45
- Converts a DateTimeIndex to a new lower frequency
46
-
47
- :param index: DateTimeIndex
48
- :param frequency: frequency string
49
- :return: DateTimeIndex
50
- """
51
- return pd.DataFrame(index=index).asfreq(frequency).index
52
-
53
- class _date_range:
54
- """
55
- This is a callable class that should be used by calling the already initiated instance: `date_range`.
56
- Given a schedule, it will return a DatetimeIndex with all of the valid datetimes at the frequency given.
57
-
58
- The schedule columns should all have the same time zone.
59
-
60
- The calculations will be made for each trading session. If the passed schedule-DataFrame doesn't have
61
- breaks, there is one trading session per day going from market_open to market_close, otherwise there are two,
62
- the first one going from market_open to break_start and the second one from break_end to market_close.
63
-
64
- *Any trading session where start == end is considered a 'no-trading session' and will always be dropped*
65
-
66
- CAVEATS:
67
- * Only "market_open", "market_close" (and, optionally, "breaak_start" and "break_end")
68
- are considered, other market times are not yet supported by this class.
69
-
70
- * If the difference between start and end of a trading session is smaller than an interval of the
71
- frequency, and closed= "right" and force_close = False, the whole session will disappear.
72
- This will also raise a warning.
73
-
74
-
75
- Signature:
76
- .__call__(self, schedule, frequency, closed='right', force_close=True, **kwargs)
77
-
78
- :param schedule: schedule of a calendar, which may or may not include break_start and break_end columns
79
- :param frequency: frequency string that is used by pd.Timedelta to calculate the timestamps
80
- this must be "1D" or higher frequency
81
- :param closed: the way the intervals are labeled
82
- 'right': use the end of the interval
83
- 'left': use the start of the interval
84
- None: (or 'both') use the end of the interval but include the start of the first interval (the open)
85
- :param force_close: how the last value of a trading session is handled
86
- True: guarantee that the close of the trading session is the last value
87
- False: guarantee that there is no value greater than the close of the trading session
88
- None: leave the last value as it is calculated based on the closed parameter
89
- :param kwargs: unused. Solely for compatibility.
90
-
91
-
92
- """
93
-
94
- def __init__(self, schedule = None, frequency= None, closed='right', force_close=True):
95
- if not closed in ("left", "right", "both", None):
96
- raise ValueError("closed must be 'left', 'right', 'both' or None.")
97
- elif not force_close in (True, False, None):
98
- raise ValueError("force_close must be True, False or None.")
99
-
100
- self.closed = closed
101
- self.force_close = force_close
102
- self.has_breaks = False
103
- if frequency is None: self.frequency = None
104
- else:
105
- self.frequency = pd.Timedelta(frequency)
106
- if self.frequency > pd.Timedelta("1D"):
107
- raise ValueError('Frequency must be 1D or higher frequency.')
108
-
109
- elif schedule.market_close.lt(schedule.market_open).any():
110
- raise ValueError("Schedule contains rows where market_close < market_open,"
111
- " please correct the schedule")
112
-
113
- if "break_start" in schedule:
114
- if not all([
115
- schedule.market_open.le(schedule.break_start).all(),
116
- schedule.break_start.le(schedule.break_end).all(),
117
- schedule.break_end.le(schedule.market_close).all()]):
118
- raise ValueError("Not all rows match the condition: "
119
- "market_open <= break_start <= break_end <= market_close, "
120
- "please correct the schedule")
121
- self.has_breaks = True
122
-
123
- def _check_overlap(self, schedule):
124
- """checks if calculated end times would overlap with the next start times.
125
- Only an issue when force_close is None and closed != left.
126
-
127
- :param schedule: pd.DataFrame with first column: 'start' and second column: 'end'
128
- :raises ValueError:"""
129
- if self.force_close is None and self.closed != "left":
130
- num_bars = self._calc_num_bars(schedule)
131
- end_times = schedule.start + num_bars * self.frequency
132
-
133
- if end_times.gt(schedule.start.shift(-1)).any():
134
- raise ValueError(f"The chosen frequency will lead to overlaps in the calculated index. "
135
- f"Either choose a higher frequency or avoid setting force_close to None "
136
- f"when setting closed to 'right', 'both' or None.")
137
-
138
- def _check_disappearing_session(self, schedule):
139
- """checks if requested frequency and schedule would lead to lost trading sessions.
140
- Only necessary when force_close = False and closed = "right".
141
-
142
- :param schedule: pd.DataFrame with first column: 'start' and second column: 'end'
143
- :raises UserWarning:"""
144
- if self.force_close is False and self.closed == "right":
145
-
146
- if (schedule.end- schedule.start).lt(self.frequency).any():
147
- warnings.warn("An interval of the chosen frequency is larger than some of the trading sessions, "
148
- "while closed== 'right' and force_close is False. This will make those trading sessions "
149
- "disappear. Use a higher frequency or change the values of closed/force_close, to "
150
- "keep this from happening.")
151
-
152
- def _calc_num_bars(self, schedule):
153
- """calculate the number of timestamps needed for each trading session.
154
-
155
- :param schedule: pd.DataFrame with first column: 'start' and second column: 'end'
156
- :return: pd.Series of float64"""
157
- return np.ceil((schedule.end - schedule.start) / self.frequency)
158
-
159
-
160
- def _calc_time_series(self, schedule):
161
- """Method used by date_range to calculate the trading index.
162
-
163
- :param schedule: pd.DataFrame with first column: 'start' and second column: 'end'
164
- :return: pd.Series of datetime64[ns, UTC]"""
165
- num_bars = self._calc_num_bars(schedule)
166
-
167
- # ---> calculate the desired timeseries:
168
- if self.closed == "left":
169
- opens = schedule.start.repeat(num_bars) # keep as is
170
- time_series = (opens.groupby(opens.index).cumcount()) * self.frequency + opens
171
- elif self.closed == "right":
172
- opens = schedule.start.repeat(num_bars) # dont add row but shift up
173
- time_series = (opens.groupby(opens.index).cumcount()+ 1) * self.frequency + opens
174
- else:
175
- num_bars += 1
176
- opens = schedule.start.repeat(num_bars) # add row but dont shift up
177
- time_series = (opens.groupby(opens.index).cumcount()) * self.frequency + opens
178
-
179
- if not self.force_close is None:
180
- time_series = time_series[time_series.le(schedule.end.repeat(num_bars))]
181
- if self.force_close:
182
- time_series = pd.concat([time_series, schedule.end]).sort_values()
183
-
184
- return time_series
185
-
186
-
187
- def __call__(self, schedule, frequency, closed='right', force_close=True, **kwargs):
188
- """
189
- See class docstring for more information.
190
-
191
- :param schedule: schedule of a calendar, which may or may not include break_start and break_end columns
192
- :param frequency: frequency string that is used by pd.Timedelta to calculate the timestamps
193
- this must be "1D" or higher frequency
194
- :param closed: the way the intervals are labeled
195
- 'right': use the end of the interval
196
- 'left': use the start of the interval
197
- None: (or 'both') use the end of the interval but include the start of the first interval
198
- :param force_close: how the last value of a trading session is handled
199
- True: guarantee that the close of the trading session is the last value
200
- False: guarantee that there is no value greater than the close of the trading session
201
- None: leave the last value as it is calculated based on the closed parameter
202
- :param kwargs: unused. Solely for compatibility.
203
- :return: pd.DatetimeIndex of datetime64[ns, UTC]
204
- """
205
- self.__init__(schedule, frequency, closed, force_close)
206
- if self.has_breaks:
207
- # rearrange the schedule, to make every row one session
208
- before = schedule[["market_open", "break_start"]].set_index(schedule["market_open"])
209
- after = schedule[["break_end", "market_close"]].set_index(schedule["break_end"])
210
- before.columns = after.columns = ["start", "end"]
211
- schedule = pd.concat([before, after]).sort_index()
212
-
213
- else:
214
- schedule = schedule.rename(columns= {"market_open": "start", "market_close": "end"})
215
-
216
- schedule = schedule[schedule.start.ne(schedule.end)] # drop the 'no-trading sessions'
217
- self._check_overlap(schedule)
218
- self._check_disappearing_session(schedule)
219
-
220
- time_series = self._calc_time_series(schedule)
221
-
222
- time_series.name = None
223
- return pd.DatetimeIndex(time_series.drop_duplicates())
224
-
225
- date_range = _date_range()
@@ -1,111 +0,0 @@
1
- import inspect
2
- from pprint import pformat
3
-
4
- def _regmeta_instance_factory(cls, name, *args, **kwargs):
5
- """
6
- :param cls(RegisteryMeta): registration meta class
7
- :param name(str): name of class that needs to be instantiated
8
- :param args(Optional(tuple)): instance positional arguments
9
- :param kwargs(Optional(dict)): instance named arguments
10
- :return: class instance
11
- """
12
- try:
13
- class_ = cls._regmeta_class_registry[name]
14
- except KeyError:
15
- raise RuntimeError(
16
- 'Class {} is not one of the registered classes: {}'.format(name, cls._regmeta_class_registry.keys()))
17
- return class_(*args, **kwargs)
18
-
19
- def _regmeta_register_class(cls, regcls, name):
20
- """
21
- :param cls(RegisteryMeta): registration base class
22
- :param regcls(class): class to be registered
23
- :param name(str): name of the class to be registered
24
- """
25
- if hasattr(regcls, 'aliases'):
26
- if regcls.aliases:
27
- for alias in regcls.aliases:
28
- cls._regmeta_class_registry[alias] = regcls
29
- else:
30
- cls._regmeta_class_registry[name] = regcls
31
- else:
32
- cls._regmeta_class_registry[name] = regcls
33
-
34
-
35
- class RegisteryMeta(type):
36
- """
37
- Metaclass used to register all classes inheriting from RegisteryMeta
38
- """
39
-
40
- def __new__(mcs, name, bases, attr):
41
- cls = super(RegisteryMeta, mcs).__new__(mcs, name, bases, attr)
42
- if not hasattr(cls, '_regmeta_class_registry'):
43
- cls._regmeta_class_registry = {}
44
- cls.factory = classmethod(_regmeta_instance_factory)
45
-
46
- return cls
47
-
48
- def __init__(cls, name, bases, attr):
49
- if not inspect.isabstract(cls):
50
- _regmeta_register_class(cls, cls, name)
51
- for b in bases:
52
- if hasattr(b, '_regmeta_class_registry'):
53
- _regmeta_register_class(b, cls, name)
54
-
55
- super(RegisteryMeta, cls).__init__(name, bases, attr)
56
-
57
- cls.regular_market_times = ProtectedDict(cls.regular_market_times)
58
- cls.open_close_map = ProtectedDict(cls.open_close_map)
59
-
60
- cls.special_market_open = cls.special_opens
61
- cls.special_market_open_adhoc = cls.special_opens_adhoc
62
-
63
- cls.special_market_close = cls.special_closes
64
- cls.special_market_close_adhoc = cls.special_closes_adhoc
65
-
66
-
67
- class ProtectedDict(dict):
68
-
69
- def __init__(self, *args, **kwargs):
70
- super().__init__(*args, **kwargs)
71
- # __init__ is bypassed when unpickling, which causes __setitem__ to fail
72
- # without the _INIT_RAN_NORMALLY flag
73
- self._INIT_RAN_NORMALLY = True
74
-
75
- def _set(self, key, value):
76
- return super().__setitem__(key, value)
77
-
78
- def _del(self, key):
79
- return super().__delitem__(key)
80
-
81
- def __setitem__(self, key, value):
82
- if not hasattr(self, "_INIT_RAN_NORMALLY"):
83
- return self._set(key, value)
84
-
85
- raise TypeError("You cannot set a value directly, you can change regular_market_times "
86
- "using .change_time, .add_time or .remove_time.")
87
-
88
- def __delitem__(self, key):
89
- if not hasattr(self, "_INIT_RAN_NORMALLY"):
90
- return self._del(key)
91
-
92
- raise TypeError("You cannot delete an item directly. You can change regular_market_times "
93
- "using .change_time, .add_time or .remove_time")
94
-
95
- def __repr__(self):
96
- return self.__class__.__name__+ "(" + super().__repr__() + ")"
97
-
98
- def __str__(self):
99
- try:
100
- formatted = pformat(dict(self), sort_dicts= False) # sort_dicts apparently not available < python3.8
101
- except TypeError:
102
- formatted = pformat(dict(self))
103
-
104
- return self.__class__.__name__+ "(\n" + formatted + "\n)"
105
-
106
- def copy(self):
107
- return self.__class__(super().copy())
108
-
109
-
110
-
111
-
@@ -1,63 +0,0 @@
1
- from datetime import time
2
-
3
- from pandas.tseries.holiday import AbstractHolidayCalendar, GoodFriday, EasterMonday
4
- from pytz import timezone
5
-
6
- from .holidays_oz import *
7
- from .market_calendar import MarketCalendar
8
-
9
- AbstractHolidayCalendar.start_date = '2011-01-01'
10
-
11
-
12
- class ASXExchangeCalendar(MarketCalendar):
13
- """
14
- Open Time: 10:00 AM, Australia/Sydney
15
- Close Time: 4:10 PM, Australia/Sydney
16
-
17
-
18
- Regularly-Observed Holidays:
19
- - New Year's Day (observed on Monday when Jan 1 is a Saturday or Sunday)
20
- - Australia Day (observed on Monday when Jan 26 is a Saturday or Sunday)
21
- - Good Friday (two days before Easter Sunday)
22
- - Easter Monday (the Monday after Easter Sunday)
23
- - ANZAC Day (April 25)
24
- - Queen's Birthday (second Monday in June)
25
- - Christmas Day (December 25, Saturday/Sunday to Monday)
26
- - Boxing Day (December 26, Saturday to Monday, Sunday to Tuesday)
27
-
28
-
29
- Regularly-Observed Early Closes:
30
- - Last Business Day before Christmas Day
31
- - Last Business Day of the Year
32
-
33
- """
34
- aliases = ['ASX']
35
- regular_market_times = {
36
- "market_open": ((None, time(10)),),
37
- "market_close": ((None, time(16,10)),)
38
- }
39
-
40
- @property
41
- def name(self):
42
- return "ASX"
43
-
44
- @property
45
- def tz(self):
46
- return timezone("Australia/Sydney")
47
-
48
- @property
49
- def regular_holidays(self):
50
- return AbstractHolidayCalendar(rules=[
51
- OZNewYearsDay,
52
- AustraliaDay,
53
- AnzacDay,
54
- QueensBirthday,
55
- Christmas,
56
- BoxingDay,
57
- GoodFriday,
58
- EasterMonday,
59
- ])
60
-
61
- @property
62
- def adhoc_holidays(self):
63
- return UniqueCloses