Skip to content

Advanced usage

Registering a new calendar

In exchange-calendars, you can use register_calendar() and register_calendar_type() to register new exchange calendar instances and types, and register_calendar_alias() to make an existing instance/type available under a different name.

This functionality naturally carries over to when the extensions provided by this package are applied via apply_extensions().

Calendar instances

When a plain exchange calendar instance is registered, it is not automatically extended. This is because extension works at the class level to derive extended classes with the additional properties from the plain ones.

import exchange_calendars as ec
import exchange_calendars_extensions as ecx

c0 = ec.get_calendar("XLON")  # Plain calendar instance.

assert isinstance(c0, ec.ExchangeCalendar)
assert not isinstance(c0, ecx.ExtendedExchangeCalendar)

ecx.apply_extensions()

# Register the instance under a different name.
ec.register_calendar("TEST", c0)

c = ec.get_calendar("TEST")

# Should be identical to the registered instance.
assert c is c0

ecx.remove_extensions()

c = ec.get_calendar("TEST")

# Should still be identical to the registered instance.
assert c is c0

Warning

When calendar instances are registered this way, they do not support runtime modifications, as this requires an extended calendar class.

Calendar classes

When registering a plain exchange calendar class, it is automatically extended.

import exchange_calendars as ec
import exchange_calendars_extensions as ecx

klass = ec.get_calendar("XLON").__class__  # Plain calendar class.

assert issubclass(klass, ec.ExchangeCalendar)
assert not issubclass(klass, ecx.ExtendedExchangeCalendar)

ecx.apply_extensions()

# Register the class under a different name.
ec.register_calendar_type("TEST", klass)

c = ec.get_calendar("TEST")

assert isinstance(c, ec.ExchangeCalendar)
assert isinstance(c, ecx.ExtendedExchangeCalendar)
assert len(c.quarterly_expiries.rules) == 0  # Should not have quarterly expiries.

ecx.remove_extensions()

c = ec.get_calendar("TEST")

assert isinstance(c, ec.ExchangeCalendar)
assert not isinstance(c, ecx.ExtendedExchangeCalendar)

Note that after the call to remove_extensions(), the original class is recovered.

Extension specifications

Most of the extended properties and behaviors of extended exchange calendars are added automatically. However, optional features like expiry day calendars require additional specification per calendar class. If undefined for an exchange, these optional features are not added when extending the class.

To register a new specification for optional features, call register_extension() before registering a new calendar class.

import exchange_calendars as ec
import exchange_calendars_extensions as ecx

klass = ec.get_calendar("XLON").__class__  # Plain calendar class.

ecx.register_extension("TEST", day_of_week_expiry=4)

ecx.apply_extensions()

# Register the class under a different name.
ec.register_calendar_type("TEST", klass)

c = ec.get_calendar("TEST")

assert isinstance(c, ec.ExchangeCalendar)
assert isinstance(c, ecx.ExtendedExchangeCalendar)
assert len(c.quarterly_expiries.rules) > 0  # Should have quarterly expiries.

Extended calendar classes

It is possible to create an extended calendar class manually. The helper function extend_class() creates an extended calendar class from any existing exchange calendar class. Extended classes can be registered with register_calendar_type() as usual.

import exchange_calendars as ec
import exchange_calendars_extensions as ecx
from exchange_calendars.exchange_calendar_xlon import XLONExchangeCalendar

klass = ecx.extend_class(XLONExchangeCalendar, day_of_week_expiry=4)

ecx.apply_extensions()

ec.register_calendar_type("TEST", klass)

c = ec.get_calendar("TEST")

assert isinstance(c, ec.ExchangeCalendar)
assert isinstance(c, ecx.ExtendedExchangeCalendar)
assert len(c.quarterly_expiries.rules) > 0  # Should have quarterly expiries.

Note

Manualy created extended exchange calendar classes support runtime modifications through change_day() just as any other calendar class after registration.

Merging holiday calendars

The exchange-calendars package uses exchange_calendars.exchange_calendar.HolidayCalendar, a subclass of pandas.tseries.holiday.AbstractHolidayCalendar. One of its assumptions is that each holiday rule has a unique name. When two calendars are merged via .merge(), only one rule per name is kept.

This creates a problem for the aggregate calendars in this package. For example, holidays_all must include every ad-hoc holiday, but ad-hoc holidays have no unique name — they all share the placeholder ad-hoc holiday. The built-in merge would therefore discard all but one of them.

To work around this, the package provides:

from exchange_calendars_extensions import merge_calendars

merged = merge_calendars([calendar_a, calendar_b, ...])

merge_calendars(calendars) concatenates all rules from the given calendars in order and returns a new HolidayCalendar subclass that filters out duplicate dates when holidays() is called.

Always use merge_calendars(...) instead of AbstractHolidayCalendar.merge(...)

When merging involves any calendar added by this package, the built-in merge may silently drop ad-hoc holidays. Use merge_calendars(...) to avoid this. Rules earlier in the list take priority when duplicates are encountered.