# exchange-calendars-extensions
[![PyPI](https://img.shields.io/pypi/v/exchange-calendars-extensions)](https://pypi.org/project/exchange-calendars-extensions/) ![Python Support](https://img.shields.io/pypi/pyversions/exchange_calendars_extensions) ![PyPI Downloads](https://img.shields.io/pypi/dd/exchange-calendars-extensions)
A Python package that transparently adds some features to the [exchange-calendars](https://pypi.org/project/exchange-calendars/)
package.
For all exchanges, this package adds the following:
- Calendars that combine existing regular and ad-hoc holidays/special open days/special close days into a single
calendar, respectively.
- Calendars for the last trading session of each month, and the last *regular* trading session of each month.
- The ability to modify existing calendars by adding or removing special days programmatically at runtime.
For select exchanges, this packages also adds:
- Calendars for additional special trading sessions, such as monthly and quarterly expiry days (aka quadruple witching).
- The ability to add or remove these additional special trading sessions programmatically at runtime.
## Combined calendars
This package adds combined calendars for holidays and special open/close days, respectively. These calendars combine
regular with ad-hoc occurrences.
Note that for special open/close days, this may aggregate days with different
open/close times into a single calendar. From the combined calendar, the open/close time for each contained day cannot
be recovered.
## Additional calendars
In addition to information that is already available in
[exchange-calendars](https://pypi.org/project/exchange-calendars/), this package also adds calendars for
the following trading sessions:
- last trading session of the month, and
- last *regular* trading session of the month.
For select exchanges (see [below](#supported-exchanges-for-monthlyquarterly-expiry)), this package also adds calendars for:
- quarterly expiry days (aka quadruple witching), and
- monthly expiry days (in all remaining months that don't have a quarterly expiry day).
Finally, a new calendar that contains all weekend days as per the underlying weekmask is also available.
## Calendar modifications
This package also adds the ability to modify existing calendars at runtime. This can be used to add or remove
- holidays (regular and ad-hoc),
- special open days (regular and ad-hoc),
- special close days (regular and ad-hoc),
- quarterly expiry days, and
- monthly expiry days.
This is useful, for example, when an exchange announces a change to the regular trading schedule on short notice, and
the next release of the `exchange-calendars` package, including these changes, is not available yet.
## Installation
The package is available on [PyPI](https://pypi.org/project/exchange-calendars-extensions/) and can be installed via
[pip](https://pip.pypa.io/en/stable/) or any other suitable package/dependency management tool, e.g.
[Poetry](https://python-poetry.org/).
```bash
pip install exchange-calendars-extensions
```
## Usage
Import the package and register extended exchange calendar classes with the `exchange_calendars` module.
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
```
This replaces the default exchange calendar classes with the extended versions.
Get an exchange calendar instance and verify that extended exchange calendars are subclasses of the abstract base
class `ecx.ExtendedExchangeCalendar`. This class inherits both from
`ec.ExchangeCalendar` and the new protocol class
`ecx.ExchangeCalendarExtensions` which defines the extended properties.
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
calendar = ec.get_calendar('XLON')
# It's still a regular exchange calendar.
assert isinstance(calendar, ec.ExchangeCalendar)
# But it's also an extended exchange calendar...
assert isinstance(calendar, ecx.ExtendedExchangeCalendar)
# ...and implements the extended protocol.
assert isinstance(calendar, ecx.ExchangeCalendarExtensions)
```
The original classes can be re-instated by calling `ecx.remove_extensions()`.
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
...
ecx.remove_extensions()
calendar = ec.get_calendar('XLON')
# It's a regular exchange calendar.
assert isinstance(calendar, ec.ExchangeCalendar)
# But it's not an extended exchange calendar anymore.
assert not isinstance(calendar, ecx.ExtendedExchangeCalendar)
assert not isinstance(calendar, ecx.ExchangeCalendarExtensions)
```
### Additional properties
Extended exchange calendars provide the following calendars as properties:
- `holidays_all`: Regular and ad-hoc holidays combined into a single calendar.
- `special_opens_all`: Regular and ad-hoc special open days combined into a single calendar.
- `special_closes_all`: Regular and ad-hoc special close days combined into a single calendar.
- `weekend_days`: All weekend days, as defined by the underlying weekmask, in a single calendar.
- `quarterly_expiries`: Quarterly expiry days, also known as quadruple witching. Many exchanges observe special business
days on which market index futures, options futures, stock options and stock futures expire, typically resulting in
increased volatility and traded volume. Quadruple witching is typically observed on the third Friday of March, June,
September and December, although some exchanges observe it on Thursday instead. Note that in the case of collisions
with holidays or special open/close days, a quarterly expiry day is usually rolled backward to the previous and
otherwise regular business day.
- `monthly_expiries`: Monthly expiry days. Similar to quarterly expiry days, but for all remaining months of the year.
Provided in a separate calendar as they typically result in less extreme trading patterns.
- `last_session_of_months`: The last trading session for each month of the year.
- `last_regular_session_of_months`: Last regular trading session of each month of the year, i.e. not a special
open/close or otherwise irregular day.
For example,
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
calendar = ec.get_calendar('XLON')
print(calendar.holidays_all.holidays(start='2020-01-01', end='2020-12-31', return_name=True))
```
will output
```text
2020-01-01 New Year's Day
2020-04-10 Good Friday
2020-04-13 Easter Monday
2020-05-08 ad-hoc holiday
2020-05-25 Spring Bank Holiday
2020-08-31 Summer Bank Holiday
2020-12-25 Christmas
2020-12-26 Boxing Day
2020-12-28 Weekend Boxing Day
dtype: object
```
Note that the ad-hoc holiday on 2020-05-08 (Queen Elizabeth II 75th anniversary) is included in the combined holiday
calendar, together with all regular holidays during the period.
Quarterly and monthly expiry days:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
calendar = ec.get_calendar('XLON')
print(calendar.quarterly_expiries.holidays(start='2023-01-01', end='2023-12-31', return_name=True))
print(calendar.monthly_expiries.holidays(start='2023-01-01', end='2023-12-31', return_name=True))
```
will output
```text
2023-03-17 quarterly expiry
2023-06-16 quarterly expiry
2023-09-15 quarterly expiry
2023-12-15 quarterly expiry
dtype: object
2023-01-20 monthly expiry
2023-02-17 monthly expiry
2023-04-21 monthly expiry
2023-05-19 monthly expiry
2023-07-21 monthly expiry
2023-08-18 monthly expiry
2023-10-20 monthly expiry
2023-11-17 monthly expiry
dtype: object
```
Last trading days of months:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
calendar = ec.get_calendar('XLON')
print(calendar.last_trading_days_of_months.holidays(start='2023-01-01', end='2023-12-31', return_name=True))
print(calendar.last_regular_trading_days_of_months.holidays(start='2023-01-01', end='2023-12-31', return_name=True))
```
will output
```text
2023-01-31 last trading day of month
2023-02-28 last trading day of month
2023-03-31 last trading day of month
2023-04-28 last trading day of month
2023-05-31 last trading day of month
2023-06-30 last trading day of month
2023-07-31 last trading day of month
2023-08-31 last trading day of month
2023-09-29 last trading day of month
2023-10-31 last trading day of month
2023-11-30 last trading day of month
2023-12-29 last trading day of month
dtype: object
2023-01-31 last regular trading day of month
2023-02-28 last regular trading day of month
2023-03-31 last regular trading day of month
2023-04-28 last regular trading day of month
2023-05-31 last regular trading day of month
2023-06-30 last regular trading day of month
2023-07-31 last regular trading day of month
2023-08-31 last regular trading day of month
2023-09-29 last regular trading day of month
2023-10-31 last regular trading day of month
2023-11-30 last regular trading day of month
2023-12-28 last regular trading day of month
dtype: object
```
Note the difference in December, where 2023-12-29 is a special close day, so 2023-12-28 is the last regular trading day
in that month.
### Adding special days
The `exchange_calendars_extensions` module provides the methods `add_holiday(...)`, `add_special_open(...)`,
`add_special_close(...)`, `add_monthly_expiry(...)` and `add_quarterly_expiry(...)` to add holidays and other types of
special days. For example,
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.add_holiday('XLON', date='2022-12-28', name='Holiday')
calendar = ec.get_calendar('XLON')
assert '2022-12-28' in calendar.regular_holidays.holidays()
assert '2022-12-28' in calendar.holidays_all.holidays()
```
will add a new holiday named `Holiday` to the calendar for the London Stock Exchange on 28 December 2022. Holidays are
always added as regular holidays to allow for an individual name.
Adding special open or close days works similarly, but needs the respective special open or close time:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.add_special_open('XLON', date='2022-12-28', time='11:00', name='Special Open')
calendar = ec.get_calendar('XLON')
assert '2022-12-28' in calendar.special_opens_all.holidays()
```
A more generic way to add a special day is via `add_day(...)` which takes either a `DaySpec` (holidays,
monthly/quarterly expiries) or `DaySpecWithTime` (special open/close days) Pydantic model:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.add_day('XLON', ecx.DaySpec(date='2022-12-27', type=ecx.DayType.HOLIDAY, name='Holiday'))
ecx.add_day('XLON', ecx.DaySpecWithTime(date='2022-12-28', type=ecx.DayType.SPECIAL_OPEN, name='Special Open', time='11:00'))
calendar = ec.get_calendar('XLON')
assert '2022-12-27' in calendar.regular_holidays.holidays()
assert '2022-12-27' in calendar.holidays_all.holidays()
assert '2022-12-28' in calendar.special_opens_all.holidays()
```
Thanks to Pydantic, an even easier way is to just use suitable dictionaries:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.add_day('XLON', {'date': '2022-12-27', 'type': 'holiday', 'name': 'Holiday'})
ecx.add_day('XLON', {'date': '2022-12-28', 'type': 'special_open', 'name': 'Special Open', 'time': '11:00'})
calendar = ec.get_calendar('XLON')
assert '2022-12-27' in calendar.regular_holidays.holidays()
assert '2022-12-27' in calendar.holidays_all.holidays()
assert '2022-12-28' in calendar.special_opens_all.holidays()
```
The dictionary format makes it particularly easy to read in changes from an external source like a file.
### Removing special sessions
To remove a day as a special day (of any type) from a calendar, use `remove_day(...)`. For example,
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.remove_day('XLON', '2022-12-27')
calendar = ec.get_calendar('XLON')
assert '2022-12-27' not in calendar.regular_holidays.holidays()
assert '2022-12-27' not in calendar.holidays_all.holidays()
```
will remove the holiday on 27 December 2022 from the calendar, thus turning this day into a regular trading day.
Removing a day via `remove_day(...)` that is not actually a special day, results in no change and does not throw an
exception.
### Specifying dates, times, and day types
Thanks to Pydantic, dates, times, and the types of special day can typically be specified in different formats and will
safely be parsed into the correct data type that is used internally.
For example, wherever the API expects a date, you may pass in a `pandas.Timestamp`, a `datetime.date` object, or simply
a string in ISO format `YYYY-MM-DD`. Similarly, wall clock times can be passed as `datetime.time` objects or as strings
in the format `HH:MM:SS` or `HH:MM`.
The enumeration type `ecx.DayType` represents types of special days, API calls accept either enumeration members or
their string value. For example, `ecx.DayType.HOLIDAY` and `'holiday'` are equivalent.
### Change visibility
Whenever a calendar has been modified programmatically, the changes are only reflected after obtaining a new exchange
calendar instance.
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
calendar = ec.get_calendar('XLON')
# Unchanged calendar.
assert '2022-12-27' in calendar.holidays_all.holidays()
assert '2022-12-28' not in calendar.holidays_all.holidays()
# Modify calendar. This clears the cache, so ec.get_calendar('XLON') will return a new instance next time.
ecx.add_holiday('XLON', '2022-12-28', 'Holiday')
ecx.remove_day('XLON', '2022-12-27')
# Changes not reflected in existing instance.
assert '2022-12-27' in calendar.holidays_all.holidays()
assert '2022-12-28' not in calendar.holidays_all.holidays()
# Get new instance.
calendar = ec.get_calendar('XLON')
# Changes reflected in new instance.
assert '2022-12-27' not in calendar.holidays_all.holidays()
assert '2022-12-28' in calendar.holidays_all.holidays()
# Revert the changes.
ecx.reset_calendar('XLON')
# Get new instance.
calendar = ec.get_calendar('XLON')
# Changes reverted in new instance.
assert '2022-12-27' in calendar.holidays_all.holidays()
assert '2022-12-28' not in calendar.holidays_all.holidays()
```
### Changesets
When modifying an exchange calendar, the changes are recorded in an `ecx.ChangeSet` associated with the corresponding
exchange. When a new calendar instance is created, the changes are applied to the calendar, as seen above.
It is also possible to create a changeset separately and then associate it with a particular exchange:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
changeset: ecx.ChangeSet = ecx.ChangeSet()
changeset.add_day({'date': '2022-12-28', 'type': 'holiday', 'name': 'Holiday'})
changeset.remove_day('2022-12-27')
ecx.update_calendar('XLON', changeset)
calendar = ec.get_calendar('XLON')
assert '2022-12-27' not in calendar.holidays_all.holidays()
assert '2022-12-28' in calendar.holidays_all.holidays()
```
Again, an entire changeset can also be created from a suitably formatted dictionary, making it particularly easy to read
in and apply changes from an external source like a file.
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
changeset: ecx.ChangeSet = ecx.ChangeSet(**{
'add': [{'date': '2022-12-28', 'type': 'holiday', 'name': 'Holiday'}],
'remove': ['2022-12-27']})
ecx.update_calendar('XLON', changeset)
calendar = ec.get_calendar('XLON')
assert '2022-12-27' not in calendar.holidays_all.holidays()
assert '2022-12-28' in calendar.holidays_all.holidays()
```
### Adding and removing the same day
The API permits to add and remove the same day as a special day. For example, the following code will add a holiday on
28 December 2022 to the calendar, and then remove the same day as well.
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.add_holiday('XLON', date='2022-12-28', name='Holiday')
ecx.remove_day('XLON', date='2022-12-28')
calendar = ec.get_calendar('XLON')
assert '2022-12-28' in calendar.holidays_all.holidays()
```
The result is that the day is a holiday in the changed calendar. These semantics of the API may be surprising, but make
more sense in a case where a day is added to change its type of special day. Consider the date `2022-12-27` which was a
holiday for the calendar `XLON` in the original version of the calendar. The following code will change the type of
special day to a special open by first removing the day (as a holiday), and then adding it back as a special open day:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.remove_day('XLON', date='2022-12-27')
ecx.add_special_open('XLON', date='2022-12-27', name='Special Open', time='11:00')
calendar = ec.get_calendar('XLON')
assert '2022-12-27' not in calendar.holidays_all.holidays()
assert '2022-12-27' in calendar.special_opens_all.holidays()
```
Removing a day does not consider the type of special day and thus will convert any type of special day into a regular
trading day (if the weekmask permits). Adding a day will add it as the specified type of special day. Together, this
allows to change the type of special day in an existing calendar from one to another.
In fact, internally, each added days is always implicitly also removed from the calendar first, so that it strictly is
not necessary (but allowed) to explicitly remove a day, and then adding it back as a different type of special day:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
# It is enough to add an existing special day with a new type to change the type of special day.
ecx.add_special_open('XLON', date='2022-12-27', name='Special Open', time='11:00')
calendar = ec.get_calendar('XLON')
# No longer a holiday.
assert '2022-12-27' not in calendar.holidays_all.holidays()
# Now a special open.
assert '2022-12-27' in calendar.special_opens_all.holidays()
```
### Changeset consistency
As seen above, changesets may contain the same day both in the list of days to add and in the list of days to remove.
However, changesets enforce consistency and will raise an exception if the same day is added more than once.
For example, the following code will raise an exception:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
ecx.add_holiday('XLON', date='2022-12-28', name='Holiday')
ecx.add_special_open('XLON', date='2022-12-28', name='Special Open', time='11:00')
```
In contrast, removing a day is an idempotent operation, i.e. doing it twice will not raise an exception and keep the
corresponding changeset the same as after the first removal.
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
ecx.remove_day('XLON', date='2022-12-27')
ecx.remove_day('XLON', date='2022-12-27')
```
### Reverting changes
It is sometimes necessary to revert individual changes made to a calendar. To that end the package provides the method
`reset_day(...)`:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.add_holiday('XLON', '2022-12-28', 'Holiday')
ecx.remove_day('XLON', '2022-12-27')
calendar = ec.get_calendar('XLON')
assert '2022-12-27' not in calendar.holidays_all.holidays()
assert '2022-12-28' in calendar.holidays_all.holidays()
ecx.reset_day('XLON', '2022-12-28')
ecx.reset_day('XLON', '2022-12-27')
calendar = ec.get_calendar('XLON')
# Calendar unchanged again.
assert '2022-12-27' in calendar.holidays_all.holidays()
assert '2022-12-28' not in calendar.holidays_all.holidays()
```
To reset an entire calendar to its original state, use the method `reset_calendar(...)` or update the calendar with an
empty ChangeSet:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
import exchange_calendars as ec
ecx.add_holiday('XLON', '2022-12-28', 'Holiday')
ecx.remove_day('XLON', '2022-12-27')
calendar = ec.get_calendar('XLON')
assert '2022-12-27' not in calendar.holidays_all.holidays()
assert '2022-12-28' in calendar.holidays_all.holidays()
# Same as ecx.update_calendar('XLON', ecx.ChangeSet())
ecx.reset_calendar('XLON')
calendar = ec.get_calendar('XLON')
# Calendar unchanged again.
assert '2022-12-27' in calendar.holidays_all.holidays()
assert '2022-12-28' not in calendar.holidays_all.holidays()
```
### Retrieving changes
For any calendar, it is possible to retrieve a copy of the associated changeset:
```python
import exchange_calendars_extensions as ecx
ecx.apply_extensions()
ecx.add_holiday('XLON', date='2022-12-28', name='Holiday')
ecx.remove_day('XLON', date='2022-12-27')
changeset: ecx.ChangeSet = ecx.get_changes_for_calendar('XLON')
print(changeset)
```
Output:
```
add=[DaySpec(date=Timestamp('2022-12-28 00:00:00'), name='Holiday', type=<DayType.HOLIDAY: 'holiday'>)] remove=[Timestamp('2022-12-27 00:00:00')]
```
Since `ecx.get_changes_for_calendar(...)` returns a copy of the changeset, any modifications to the returned changeset
will not affect the calendar.
To get the changesets for all calendars, use `ecx.get_changes_for_all_calendars()`. This returns a dictionary that
mapping the exchange name/key to a copy of the corresponding changeset.
## Supported exchanges for monthly/quarterly expiry
This package currently provides support for monthly/quarterly expiry calendars for the following subset of exchanges
from `exchange_calendars`:
- ASEX
- BMEX
- XAMS
- XBRU
- XBUD
- XCSE
- XDUB
- XETR
- XHEL
- XIST
- XJSE
- XLIS
- XLON
- XMAD
- XMIL
- XNAS
- XNYS
- XOSL
- XPAR
- XPRA
- XSTO
- XSWX
- XTAE
- XTSE
- XWAR
- XWBO
## Advanced usage
### Adding an extended calendar for a new exchange
To facilitate the creation of extended exchange calendar classes, the function `extend_class` is provided in the
sub-module `exchange_calendars_extensions.holiday_calendar`.
```python
from exchange_calendars.exchange_calendar_xlon import XLONExchangeCalendar
from exchange_calendars_extensions import extend_class
xlon_extended_cls = extend_class(XLONExchangeCalendar, day_of_week_expiry=4)
```
The first argument to `extend_class` should be the class of the exchange calendar to extend. The second and optional
parameter, which defaults to `None`, is the day of the week on which expiry days are normally observed. If this parameter
is `None`, this assumes that the underlying exchange does not support monthly or quarterly expiry days and the respective
calendars will not be added.
The returned extended class directly inherits from the passed base class and adds the additional attributes like
`holidays_all` et cetera. The returned class also supports programmatic modifications using the corresponding exchange
key of the parent class.
To register a new extended class for an exchange, use the `register_extension()` function before calling
`apply_extensions()`.
```python
from exchange_calendars_extensions import register_extension, apply_extensions
register_extension(key, cls)
apply_extensions()
...
```
Here, `key` should be the name, i.e. not an alias, under which the extended class is registered with the
`exchange_calendars` package, and `cls` should be the extended class.
## Caveat: Merging holiday calendars
For the various calendars, [exchange-calendars](https://pypi.org/project/exchange-calendars/) defines and uses the class
`exchange_calendars.exchange_calendar.HolidayCalendar` which is a direct subclass of the abstract base class
`pandas.tseries.holiday.AbstractHolidayCalendar`.
One of the assumptions of `AbstractHolidayCalendar` is that each contained rule that defines a holiday has a unique
name. Thus, when merging two calendars via the `.merge()` method, the resulting calendar will only retain a single rule
for each name, eliminating any duplicates.
This creates a problem with the calendars provided by this package. For example, constructing the holiday calendar
backing `holidays_all` requires to add a rule for each ad-hoc holiday. However, since ad-hoc holidays don't define a
unique name, each rule would either have to generate a unique name for itself, or use the same name as the other rules.
This package uses the latter approach, i.e. all ad-hoc holidays are assigned the same name `ad-hoc holiday`.
As a result, the built-in merge functionality of `AbstractHolidayCalendar` would eliminate all but one of the ad-hoc
holidays when merging with another calendar. This is not the desired behavior.
To avoid this problem, this package defines the function `merge_calendars(calendars: Iterable[AbstractHolidayCalendar])`
which returns a calendar that simply concatenates, in order, all rules from the passed-in calendars. The returned
calendar is a subclass of `HolidayCalendar` that handles possible duplicates by filtering them out before returning
from a call to `holidays()`.
**In essence: Always use `merge_calendars(...)` instead of `AbstractHolidayCalendar.merge(...)` when merging involves
any of the calendars added by this package. Keep in mind that for duplicate elimination, rules more to the front of the
list have higher priority.**
## Contributing
Contributions are welcome. Please open an issue or submit a pull request on GitHub.
Raw data
{
"_id": null,
"home_page": "https://github.com/jenskeiner/exchange_calendars_extensions/",
"name": "exchange-calendars-extensions",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.9",
"maintainer_email": null,
"keywords": "exchange, calendar, trading, holidays",
"author": "Jens Keiner",
"author_email": "jens.keiner@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/1b/86/895ef0df4980f26b82367620eb2590a431fdf9b8b70694e50469eb15194f/exchange_calendars_extensions-0.8.2.tar.gz",
"platform": null,
"description": "# exchange-calendars-extensions\n[![PyPI](https://img.shields.io/pypi/v/exchange-calendars-extensions)](https://pypi.org/project/exchange-calendars-extensions/) ![Python Support](https://img.shields.io/pypi/pyversions/exchange_calendars_extensions) ![PyPI Downloads](https://img.shields.io/pypi/dd/exchange-calendars-extensions)\n\nA Python package that transparently adds some features to the [exchange-calendars](https://pypi.org/project/exchange-calendars/) \npackage.\n\nFor all exchanges, this package adds the following:\n- Calendars that combine existing regular and ad-hoc holidays/special open days/special close days into a single \n calendar, respectively.\n- Calendars for the last trading session of each month, and the last *regular* trading session of each month.\n- The ability to modify existing calendars by adding or removing special days programmatically at runtime.\n\nFor select exchanges, this packages also adds:\n- Calendars for additional special trading sessions, such as monthly and quarterly expiry days (aka quadruple witching).\n- The ability to add or remove these additional special trading sessions programmatically at runtime.\n\n## Combined calendars\nThis package adds combined calendars for holidays and special open/close days, respectively. These calendars combine \nregular with ad-hoc occurrences.\n\nNote that for special open/close days, this may aggregate days with different \nopen/close times into a single calendar. From the combined calendar, the open/close time for each contained day cannot \nbe recovered.\n\n## Additional calendars\nIn addition to information that is already available in \n[exchange-calendars](https://pypi.org/project/exchange-calendars/), this package also adds calendars for \nthe following trading sessions:\n- last trading session of the month, and\n- last *regular* trading session of the month.\n\nFor select exchanges (see [below](#supported-exchanges-for-monthlyquarterly-expiry)), this package also adds calendars for:\n- quarterly expiry days (aka quadruple witching), and\n- monthly expiry days (in all remaining months that don't have a quarterly expiry day).\n\nFinally, a new calendar that contains all weekend days as per the underlying weekmask is also available.\n\n## Calendar modifications\nThis package also adds the ability to modify existing calendars at runtime. This can be used to add or remove\n- holidays (regular and ad-hoc),\n- special open days (regular and ad-hoc),\n- special close days (regular and ad-hoc),\n- quarterly expiry days, and\n- monthly expiry days.\n\nThis is useful, for example, when an exchange announces a change to the regular trading schedule on short notice, and \nthe next release of the `exchange-calendars` package, including these changes, is not available yet.\n\n## Installation\nThe package is available on [PyPI](https://pypi.org/project/exchange-calendars-extensions/) and can be installed via \n[pip](https://pip.pypa.io/en/stable/) or any other suitable package/dependency management tool, e.g. \n[Poetry](https://python-poetry.org/).\n\n```bash\npip install exchange-calendars-extensions\n```\n\n## Usage\nImport the package and register extended exchange calendar classes with the `exchange_calendars` module.\n```python\nimport exchange_calendars_extensions as ecx\n\necx.apply_extensions()\n```\nThis replaces the default exchange calendar classes with the extended versions. \n\nGet an exchange calendar instance and verify that extended exchange calendars are subclasses of the abstract base \nclass `ecx.ExtendedExchangeCalendar`. This class inherits both from \n`ec.ExchangeCalendar` and the new protocol class \n`ecx.ExchangeCalendarExtensions` which defines the extended properties.\n\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\ncalendar = ec.get_calendar('XLON')\n\n# It's still a regular exchange calendar.\nassert isinstance(calendar, ec.ExchangeCalendar)\n\n# But it's also an extended exchange calendar...\nassert isinstance(calendar, ecx.ExtendedExchangeCalendar)\n# ...and implements the extended protocol.\nassert isinstance(calendar, ecx.ExchangeCalendarExtensions)\n```\n\nThe original classes can be re-instated by calling `ecx.remove_extensions()`.\n\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\n...\n\necx.remove_extensions()\n\ncalendar = ec.get_calendar('XLON')\n\n# It's a regular exchange calendar.\nassert isinstance(calendar, ec.ExchangeCalendar)\n\n# But it's not an extended exchange calendar anymore.\nassert not isinstance(calendar, ecx.ExtendedExchangeCalendar)\nassert not isinstance(calendar, ecx.ExchangeCalendarExtensions)\n```\n\n### Additional properties\nExtended exchange calendars provide the following calendars as properties:\n- `holidays_all`: Regular and ad-hoc holidays combined into a single calendar.\n- `special_opens_all`: Regular and ad-hoc special open days combined into a single calendar.\n- `special_closes_all`: Regular and ad-hoc special close days combined into a single calendar.\n- `weekend_days`: All weekend days, as defined by the underlying weekmask, in a single calendar.\n- `quarterly_expiries`: Quarterly expiry days, also known as quadruple witching. Many exchanges observe special business\n days on which market index futures, options futures, stock options and stock futures expire, typically resulting in \n increased volatility and traded volume. Quadruple witching is typically observed on the third Friday of March, June,\n September and December, although some exchanges observe it on Thursday instead. Note that in the case of collisions \n with holidays or special open/close days, a quarterly expiry day is usually rolled backward to the previous and \n otherwise regular business day. \n- `monthly_expiries`: Monthly expiry days. Similar to quarterly expiry days, but for all remaining months of the year. \n Provided in a separate calendar as they typically result in less extreme trading patterns.\n- `last_session_of_months`: The last trading session for each month of the year.\n- `last_regular_session_of_months`: Last regular trading session of each month of the year, i.e. not a special \n open/close or otherwise irregular day.\n\nFor example,\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\ncalendar = ec.get_calendar('XLON')\nprint(calendar.holidays_all.holidays(start='2020-01-01', end='2020-12-31', return_name=True))\n```\nwill output\n```text\n2020-01-01 New Year's Day\n2020-04-10 Good Friday\n2020-04-13 Easter Monday\n2020-05-08 ad-hoc holiday\n2020-05-25 Spring Bank Holiday\n2020-08-31 Summer Bank Holiday\n2020-12-25 Christmas\n2020-12-26 Boxing Day\n2020-12-28 Weekend Boxing Day\ndtype: object\n```\nNote that the ad-hoc holiday on 2020-05-08 (Queen Elizabeth II 75th anniversary) is included in the combined holiday \ncalendar, together with all regular holidays during the period.\n\nQuarterly and monthly expiry days:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\ncalendar = ec.get_calendar('XLON')\nprint(calendar.quarterly_expiries.holidays(start='2023-01-01', end='2023-12-31', return_name=True))\nprint(calendar.monthly_expiries.holidays(start='2023-01-01', end='2023-12-31', return_name=True))\n```\nwill output\n```text\n2023-03-17 quarterly expiry\n2023-06-16 quarterly expiry\n2023-09-15 quarterly expiry\n2023-12-15 quarterly expiry\ndtype: object\n2023-01-20 monthly expiry\n2023-02-17 monthly expiry\n2023-04-21 monthly expiry\n2023-05-19 monthly expiry\n2023-07-21 monthly expiry\n2023-08-18 monthly expiry\n2023-10-20 monthly expiry\n2023-11-17 monthly expiry\ndtype: object\n```\n\nLast trading days of months:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\n\ncalendar = ec.get_calendar('XLON')\nprint(calendar.last_trading_days_of_months.holidays(start='2023-01-01', end='2023-12-31', return_name=True))\nprint(calendar.last_regular_trading_days_of_months.holidays(start='2023-01-01', end='2023-12-31', return_name=True))\n```\nwill output\n```text\n2023-01-31 last trading day of month\n2023-02-28 last trading day of month\n2023-03-31 last trading day of month\n2023-04-28 last trading day of month\n2023-05-31 last trading day of month\n2023-06-30 last trading day of month\n2023-07-31 last trading day of month\n2023-08-31 last trading day of month\n2023-09-29 last trading day of month\n2023-10-31 last trading day of month\n2023-11-30 last trading day of month\n2023-12-29 last trading day of month\ndtype: object\n2023-01-31 last regular trading day of month\n2023-02-28 last regular trading day of month\n2023-03-31 last regular trading day of month\n2023-04-28 last regular trading day of month\n2023-05-31 last regular trading day of month\n2023-06-30 last regular trading day of month\n2023-07-31 last regular trading day of month\n2023-08-31 last regular trading day of month\n2023-09-29 last regular trading day of month\n2023-10-31 last regular trading day of month\n2023-11-30 last regular trading day of month\n2023-12-28 last regular trading day of month\ndtype: object\n```\nNote the difference in December, where 2023-12-29 is a special close day, so 2023-12-28 is the last regular trading day\nin that month.\n\n### Adding special days\n\nThe `exchange_calendars_extensions` module provides the methods `add_holiday(...)`, `add_special_open(...)`, \n`add_special_close(...)`, `add_monthly_expiry(...)` and `add_quarterly_expiry(...)` to add holidays and other types of \nspecial days. For example,\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.add_holiday('XLON', date='2022-12-28', name='Holiday')\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-28' in calendar.regular_holidays.holidays()\nassert '2022-12-28' in calendar.holidays_all.holidays()\n```\nwill add a new holiday named `Holiday` to the calendar for the London Stock Exchange on 28 December 2022. Holidays are \nalways added as regular holidays to allow for an individual name.\n\nAdding special open or close days works similarly, but needs the respective special open or close time:\n```python\nimport exchange_calendars_extensions as ecx\n\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.add_special_open('XLON', date='2022-12-28', time='11:00', name='Special Open')\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-28' in calendar.special_opens_all.holidays()\n```\n\nA more generic way to add a special day is via `add_day(...)` which takes either a `DaySpec` (holidays, \nmonthly/quarterly expiries) or `DaySpecWithTime` (special open/close days) Pydantic model:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.add_day('XLON', ecx.DaySpec(date='2022-12-27', type=ecx.DayType.HOLIDAY, name='Holiday'))\necx.add_day('XLON', ecx.DaySpecWithTime(date='2022-12-28', type=ecx.DayType.SPECIAL_OPEN, name='Special Open', time='11:00'))\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-27' in calendar.regular_holidays.holidays()\nassert '2022-12-27' in calendar.holidays_all.holidays()\nassert '2022-12-28' in calendar.special_opens_all.holidays()\n```\n\nThanks to Pydantic, an even easier way is to just use suitable dictionaries: \n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.add_day('XLON', {'date': '2022-12-27', 'type': 'holiday', 'name': 'Holiday'})\necx.add_day('XLON', {'date': '2022-12-28', 'type': 'special_open', 'name': 'Special Open', 'time': '11:00'})\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-27' in calendar.regular_holidays.holidays()\nassert '2022-12-27' in calendar.holidays_all.holidays()\nassert '2022-12-28' in calendar.special_opens_all.holidays()\n```\nThe dictionary format makes it particularly easy to read in changes from an external source like a file.\n\n### Removing special sessions\n\nTo remove a day as a special day (of any type) from a calendar, use `remove_day(...)`. For example,\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.remove_day('XLON', '2022-12-27')\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-27' not in calendar.regular_holidays.holidays()\nassert '2022-12-27' not in calendar.holidays_all.holidays()\n```\nwill remove the holiday on 27 December 2022 from the calendar, thus turning this day into a regular trading day.\n\nRemoving a day via `remove_day(...)` that is not actually a special day, results in no change and does not throw an \nexception.\n\n### Specifying dates, times, and day types\nThanks to Pydantic, dates, times, and the types of special day can typically be specified in different formats and will \nsafely be parsed into the correct data type that is used internally.\n\nFor example, wherever the API expects a date, you may pass in a `pandas.Timestamp`, a `datetime.date` object, or simply\na string in ISO format `YYYY-MM-DD`. Similarly, wall clock times can be passed as `datetime.time` objects or as strings \nin the format `HH:MM:SS` or `HH:MM`.\n\nThe enumeration type `ecx.DayType` represents types of special days, API calls accept either enumeration members or \ntheir string value. For example, `ecx.DayType.HOLIDAY` and `'holiday'` are equivalent.\n\n### Change visibility\nWhenever a calendar has been modified programmatically, the changes are only reflected after obtaining a new exchange \ncalendar instance.\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\ncalendar = ec.get_calendar('XLON')\n\n# Unchanged calendar.\nassert '2022-12-27' in calendar.holidays_all.holidays()\nassert '2022-12-28' not in calendar.holidays_all.holidays()\n\n# Modify calendar. This clears the cache, so ec.get_calendar('XLON') will return a new instance next time.\necx.add_holiday('XLON', '2022-12-28', 'Holiday')\necx.remove_day('XLON', '2022-12-27')\n\n# Changes not reflected in existing instance.\nassert '2022-12-27' in calendar.holidays_all.holidays()\nassert '2022-12-28' not in calendar.holidays_all.holidays()\n\n# Get new instance.\ncalendar = ec.get_calendar('XLON')\n\n# Changes reflected in new instance.\nassert '2022-12-27' not in calendar.holidays_all.holidays()\nassert '2022-12-28' in calendar.holidays_all.holidays()\n\n# Revert the changes.\necx.reset_calendar('XLON')\n\n# Get new instance.\ncalendar = ec.get_calendar('XLON')\n\n# Changes reverted in new instance.\nassert '2022-12-27' in calendar.holidays_all.holidays()\nassert '2022-12-28' not in calendar.holidays_all.holidays()\n```\n\n### Changesets\nWhen modifying an exchange calendar, the changes are recorded in an `ecx.ChangeSet` associated with the corresponding \nexchange. When a new calendar instance is created, the changes are applied to the calendar, as seen above.\n\nIt is also possible to create a changeset separately and then associate it with a particular exchange:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\nchangeset: ecx.ChangeSet = ecx.ChangeSet()\nchangeset.add_day({'date': '2022-12-28', 'type': 'holiday', 'name': 'Holiday'})\nchangeset.remove_day('2022-12-27')\n\necx.update_calendar('XLON', changeset)\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-27' not in calendar.holidays_all.holidays()\nassert '2022-12-28' in calendar.holidays_all.holidays()\n```\nAgain, an entire changeset can also be created from a suitably formatted dictionary, making it particularly easy to read\nin and apply changes from an external source like a file.\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\nchangeset: ecx.ChangeSet = ecx.ChangeSet(**{\n 'add': [{'date': '2022-12-28', 'type': 'holiday', 'name': 'Holiday'}], \n 'remove': ['2022-12-27']})\n\necx.update_calendar('XLON', changeset)\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-27' not in calendar.holidays_all.holidays()\nassert '2022-12-28' in calendar.holidays_all.holidays()\n```\n\n### Adding and removing the same day\n\nThe API permits to add and remove the same day as a special day. For example, the following code will add a holiday on\n28 December 2022 to the calendar, and then remove the same day as well.\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.add_holiday('XLON', date='2022-12-28', name='Holiday')\necx.remove_day('XLON', date='2022-12-28')\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-28' in calendar.holidays_all.holidays()\n```\nThe result is that the day is a holiday in the changed calendar. These semantics of the API may be surprising, but make \nmore sense in a case where a day is added to change its type of special day. Consider the date `2022-12-27` which was a \nholiday for the calendar `XLON` in the original version of the calendar. The following code will change the type of \nspecial day to a special open by first removing the day (as a holiday), and then adding it back as a special open day:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.remove_day('XLON', date='2022-12-27')\necx.add_special_open('XLON', date='2022-12-27', name='Special Open', time='11:00')\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-27' not in calendar.holidays_all.holidays()\nassert '2022-12-27' in calendar.special_opens_all.holidays()\n```\nRemoving a day does not consider the type of special day and thus will convert any type of special day into a regular\ntrading day (if the weekmask permits). Adding a day will add it as the specified type of special day. Together, this \nallows to change the type of special day in an existing calendar from one to another.\n\nIn fact, internally, each added days is always implicitly also removed from the calendar first, so that it strictly is \nnot necessary (but allowed) to explicitly remove a day, and then adding it back as a different type of special day:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\n# It is enough to add an existing special day with a new type to change the type of special day.\necx.add_special_open('XLON', date='2022-12-27', name='Special Open', time='11:00')\n\ncalendar = ec.get_calendar('XLON')\n\n# No longer a holiday.\nassert '2022-12-27' not in calendar.holidays_all.holidays()\n# Now a special open.\nassert '2022-12-27' in calendar.special_opens_all.holidays()\n```\n\n### Changeset consistency\nAs seen above, changesets may contain the same day both in the list of days to add and in the list of days to remove.\nHowever, changesets enforce consistency and will raise an exception if the same day is added more than once.\nFor example, the following code will raise an exception:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\n\necx.add_holiday('XLON', date='2022-12-28', name='Holiday')\necx.add_special_open('XLON', date='2022-12-28', name='Special Open', time='11:00')\n```\nIn contrast, removing a day is an idempotent operation, i.e. doing it twice will not raise an exception and keep the \ncorresponding changeset the same as after the first removal. \n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\n\necx.remove_day('XLON', date='2022-12-27')\necx.remove_day('XLON', date='2022-12-27')\n```\n\n### Reverting changes\n\nIt is sometimes necessary to revert individual changes made to a calendar. To that end the package provides the method \n`reset_day(...)`:\n\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.add_holiday('XLON', '2022-12-28', 'Holiday')\necx.remove_day('XLON', '2022-12-27')\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-27' not in calendar.holidays_all.holidays()\nassert '2022-12-28' in calendar.holidays_all.holidays()\n\necx.reset_day('XLON', '2022-12-28')\necx.reset_day('XLON', '2022-12-27')\n\ncalendar = ec.get_calendar('XLON')\n\n# Calendar unchanged again.\nassert '2022-12-27' in calendar.holidays_all.holidays()\nassert '2022-12-28' not in calendar.holidays_all.holidays()\n```\n\nTo reset an entire calendar to its original state, use the method `reset_calendar(...)` or update the calendar with an \nempty ChangeSet:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\nimport exchange_calendars as ec\n\necx.add_holiday('XLON', '2022-12-28', 'Holiday')\necx.remove_day('XLON', '2022-12-27')\n\ncalendar = ec.get_calendar('XLON')\n\nassert '2022-12-27' not in calendar.holidays_all.holidays()\nassert '2022-12-28' in calendar.holidays_all.holidays()\n\n# Same as ecx.update_calendar('XLON', ecx.ChangeSet())\necx.reset_calendar('XLON')\n\ncalendar = ec.get_calendar('XLON')\n\n# Calendar unchanged again.\nassert '2022-12-27' in calendar.holidays_all.holidays()\nassert '2022-12-28' not in calendar.holidays_all.holidays()\n```\n\n### Retrieving changes\nFor any calendar, it is possible to retrieve a copy of the associated changeset:\n```python\nimport exchange_calendars_extensions as ecx\necx.apply_extensions()\n\necx.add_holiday('XLON', date='2022-12-28', name='Holiday')\necx.remove_day('XLON', date='2022-12-27')\n\nchangeset: ecx.ChangeSet = ecx.get_changes_for_calendar('XLON')\nprint(changeset)\n```\nOutput:\n```\nadd=[DaySpec(date=Timestamp('2022-12-28 00:00:00'), name='Holiday', type=<DayType.HOLIDAY: 'holiday'>)] remove=[Timestamp('2022-12-27 00:00:00')]\n```\n\nSince `ecx.get_changes_for_calendar(...)` returns a copy of the changeset, any modifications to the returned changeset\nwill not affect the calendar.\n\nTo get the changesets for all calendars, use `ecx.get_changes_for_all_calendars()`. This returns a dictionary that \nmapping the exchange name/key to a copy of the corresponding changeset. \n\n## Supported exchanges for monthly/quarterly expiry\nThis package currently provides support for monthly/quarterly expiry calendars for the following subset of exchanges \nfrom `exchange_calendars`:\n- ASEX\n- BMEX\n- XAMS\n- XBRU\n- XBUD\n- XCSE\n- XDUB\n- XETR\n- XHEL\n- XIST\n- XJSE\n- XLIS\n- XLON\n- XMAD\n- XMIL\n- XNAS\n- XNYS\n- XOSL\n- XPAR\n- XPRA\n- XSTO\n- XSWX\n- XTAE\n- XTSE\n- XWAR\n- XWBO\n\n## Advanced usage\n\n### Adding an extended calendar for a new exchange\n\nTo facilitate the creation of extended exchange calendar classes, the function `extend_class` is provided in the \nsub-module `exchange_calendars_extensions.holiday_calendar`.\n\n```python\nfrom exchange_calendars.exchange_calendar_xlon import XLONExchangeCalendar\nfrom exchange_calendars_extensions import extend_class\n\nxlon_extended_cls = extend_class(XLONExchangeCalendar, day_of_week_expiry=4)\n```\nThe first argument to `extend_class` should be the class of the exchange calendar to extend. The second and optional\nparameter, which defaults to `None`, is the day of the week on which expiry days are normally observed. If this parameter \nis `None`, this assumes that the underlying exchange does not support monthly or quarterly expiry days and the respective\ncalendars will not be added.\n\nThe returned extended class directly inherits from the passed base class and adds the additional attributes like \n`holidays_all` et cetera. The returned class also supports programmatic modifications using the corresponding exchange \nkey of the parent class.\n\nTo register a new extended class for an exchange, use the `register_extension()` function before calling \n`apply_extensions()`.\n```python\nfrom exchange_calendars_extensions import register_extension, apply_extensions\n\nregister_extension(key, cls)\napply_extensions()\n...\n```\nHere, `key` should be the name, i.e. not an alias, under which the extended class is registered with the \n`exchange_calendars` package, and `cls` should be the extended class.\n\n## Caveat: Merging holiday calendars\nFor the various calendars, [exchange-calendars](https://pypi.org/project/exchange-calendars/) defines and uses the class\n`exchange_calendars.exchange_calendar.HolidayCalendar` which is a direct subclass of the abstract base class\n`pandas.tseries.holiday.AbstractHolidayCalendar`.\n\nOne of the assumptions of `AbstractHolidayCalendar` is that each contained rule that defines a holiday has a unique \nname. Thus, when merging two calendars via the `.merge()` method, the resulting calendar will only retain a single rule \nfor each name, eliminating any duplicates.\n\nThis creates a problem with the calendars provided by this package. For example, constructing the holiday calendar \nbacking `holidays_all` requires to add a rule for each ad-hoc holiday. However, since ad-hoc holidays don't define a \nunique name, each rule would either have to generate a unique name for itself, or use the same name as the other rules. \nThis package uses the latter approach, i.e. all ad-hoc holidays are assigned the same name `ad-hoc holiday`.\n\nAs a result, the built-in merge functionality of `AbstractHolidayCalendar` would eliminate all but one of the ad-hoc \nholidays when merging with another calendar. This is not the desired behavior.\n\nTo avoid this problem, this package defines the function `merge_calendars(calendars: Iterable[AbstractHolidayCalendar])`\nwhich returns a calendar that simply concatenates, in order, all rules from the passed-in calendars. The returned \ncalendar is a subclass of `HolidayCalendar` that handles possible duplicates by filtering them out before returning\nfrom a call to `holidays()`.\n\n**In essence: Always use `merge_calendars(...)` instead of `AbstractHolidayCalendar.merge(...)` when merging involves \nany of the calendars added by this package. Keep in mind that for duplicate elimination, rules more to the front of the\nlist have higher priority.**\n\n## Contributing\nContributions are welcome. Please open an issue or submit a pull request on GitHub.\n\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Extensions of the exchange-calendars package",
"version": "0.8.2",
"project_urls": {
"Documentation": "https://github.com/jenskeiner/exchange_calendars_extensions/tree/main/docs/",
"Homepage": "https://github.com/jenskeiner/exchange_calendars_extensions/",
"Repository": "https://github.com/jenskeiner/exchange_calendars_extensions/"
},
"split_keywords": [
"exchange",
" calendar",
" trading",
" holidays"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "404310b4bae1e4c403c60576cb9b5623531888c86411b02f93c27802532eb24d",
"md5": "0ca5ce5938ac73e5eb2ba1cba5ab330f",
"sha256": "fb4e6973275e51a9616123dfbfc3305bfa9b367e5672f9b550a83262eb33374a"
},
"downloads": -1,
"filename": "exchange_calendars_extensions-0.8.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "0ca5ce5938ac73e5eb2ba1cba5ab330f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.9",
"size": 30496,
"upload_time": "2024-06-21T14:35:19",
"upload_time_iso_8601": "2024-06-21T14:35:19.543222Z",
"url": "https://files.pythonhosted.org/packages/40/43/10b4bae1e4c403c60576cb9b5623531888c86411b02f93c27802532eb24d/exchange_calendars_extensions-0.8.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "1b86895ef0df4980f26b82367620eb2590a431fdf9b8b70694e50469eb15194f",
"md5": "57bf827eee1f805cb8ac9f77e0db2551",
"sha256": "c46e5b9460d26d4bad1b0da31356e0ecdbb1e4d622a727179d842ecb462c90ac"
},
"downloads": -1,
"filename": "exchange_calendars_extensions-0.8.2.tar.gz",
"has_sig": false,
"md5_digest": "57bf827eee1f805cb8ac9f77e0db2551",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.9",
"size": 33924,
"upload_time": "2024-06-21T14:35:21",
"upload_time_iso_8601": "2024-06-21T14:35:21.307377Z",
"url": "https://files.pythonhosted.org/packages/1b/86/895ef0df4980f26b82367620eb2590a431fdf9b8b70694e50469eb15194f/exchange_calendars_extensions-0.8.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-21 14:35:21",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jenskeiner",
"github_project": "exchange_calendars_extensions",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "exchange-calendars-extensions"
}