├── tests ├── __init__.py ├── test_24_7_calendar.py ├── test_exchange_calendar_cme_globex_grains.py ├── test_tsx_calendar.py ├── test_bse_calendar.py ├── test_eurex_calendar.py ├── test_iex_calendar.py ├── test_sse_calendar.py ├── test_cme_agriculture_calendar.py ├── test_hkex_calendar.py ├── test_ice_calendar.py ├── test_xtae_calendar.py ├── test_cme_equity_calendar.py ├── test_bmf_calendar.py ├── test_asx_calendar.py ├── test_cme_bond_calendar.py ├── test_cboe_calendars.py ├── test_six_calendar.py ├── test_eurex_fixed_income_calendar.py ├── test_ose_calendar.py ├── test_lse_calendar.py ├── test_class_registry.py ├── test_exchange_calendar_cme_globex_energy_and_metals.py └── test_exchange_calendar_cme_globex_fx.py ├── pandas_market_calendars ├── calendars │ ├── __init__.py │ ├── ice.py │ ├── eurex_fixed_income.py │ ├── asx.py │ ├── eurex.py │ ├── cme_globex_fx.py │ ├── six.py │ ├── ose.py │ ├── cme_globex_base.py │ ├── lse.py │ ├── cme_globex_equities.py │ ├── jpx.py │ ├── cboe.py │ ├── cme_globex_fixed_income.py │ ├── mirror.py │ ├── tsx.py │ ├── iex.py │ ├── cme_globex_agriculture.py │ ├── cme_globex_crypto.py │ ├── bmf.py │ └── cme_globex_energy_and_metals.py ├── holidays │ ├── __init__.py │ ├── oz.py │ ├── uk.py │ └── cme_globex.py ├── __init__.py ├── calendar_registry.py └── class_registry.py ├── docs ├── requirements.txt ├── modules.rst ├── make_docs.bat ├── make_examples.bat ├── index.rst ├── conf.py ├── new_market.rst ├── calendars.rst └── pandas_market_calendars.rst ├── CODE_OF_CONDUCT.md ├── .github └── workflows │ ├── check_branch.yml │ ├── test_runner.yml │ └── release.yml ├── .readthedocs.yaml ├── LICENSE ├── .gitignore ├── CONTRIBUTING.md ├── pyproject.toml └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pandas_market_calendars/holidays/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | pandas_market_calendars 2 | ======================= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | pandas_market_calendars 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters. 4 | 5 | For more information please visit the [No Code of Conduct](https://nocodeofconduct.com) homepage. 6 | -------------------------------------------------------------------------------- /docs/make_docs.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | REM Makes the Sphinx documentation files 3 | 4 | FOR %%a IN (%~dp0\.) do set SOURCE=%%~dpa 5 | set OLD_PYTHONPATH=%PYTHONPATH% 6 | set PYTHONPATH=%PYTHONPATH%;%SOURCE% 7 | 8 | sphinx-apidoc -f -o . ../pandas_market_calendars 9 | ./make.bat html 10 | set PYTHONPATH=%OLD_PYTHONPATH% 11 | -------------------------------------------------------------------------------- /docs/make_examples.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | REM Makes the Sphinx documentation files from the Jupyter Notebook examples 3 | 4 | FOR %%a IN (%~dp0\.) do set SOURCE=%%~dpa 5 | set OLD_PYTHONPATH=%PYTHONPATH% 6 | set PYTHONPATH=%PYTHONPATH%;%SOURCE% 7 | 8 | jupyter nbconvert --to rst --execute --output-dir . --output usage.rst ..\examples\usage.ipynb 9 | set PYTHONPATH=%OLD_PYTHONPATH% 10 | -------------------------------------------------------------------------------- /.github/workflows/check_branch.yml: -------------------------------------------------------------------------------- 1 | name: check_branch 2 | on: 3 | pull_request_target: 4 | branches: 5 | - master 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | wrong_branch: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.event.pull_request.head.repo.fork || github.head_ref != 'dev' }} 13 | 14 | steps: 15 | - uses: superbrothers/close-pull-request@v3 16 | with: 17 | comment: Please re-open this PR against the dev branch. For more information, consult CONTRIBUTING.md. 18 | - run: echo PR against master that is not based on dev && exit 1 19 | -------------------------------------------------------------------------------- /tests/test_24_7_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas.testing import assert_index_equal 3 | 4 | import pandas_market_calendars as mcal 5 | 6 | 7 | def test_weekends(): 8 | actual = mcal.get_calendar("24/7").schedule("2012-07-01", "2012-07-10").index 9 | 10 | expected = pd.DatetimeIndex( 11 | [ 12 | pd.Timestamp("2012-07-01"), 13 | pd.Timestamp("2012-07-02"), 14 | pd.Timestamp("2012-07-03"), 15 | pd.Timestamp("2012-07-04"), 16 | pd.Timestamp("2012-07-05"), 17 | pd.Timestamp("2012-07-06"), 18 | pd.Timestamp("2012-07-07"), 19 | pd.Timestamp("2012-07-08"), 20 | pd.Timestamp("2012-07-09"), 21 | pd.Timestamp("2012-07-10"), 22 | ] 23 | ) 24 | 25 | assert_index_equal(actual, expected) 26 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pandas_market_calendars documentation master file, created by 2 | sphinx-quickstart on Tue Dec 27 08:02:38 2016. 3 | 4 | .. include:: ../README.rst 5 | 6 | Updates 7 | ------- 8 | .. toctree:: 9 | :maxdepth: 1 10 | 11 | change_log.rst 12 | 13 | Markets & Exchanges 14 | ------------------- 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | calendars.rst 19 | 20 | Package Contents 21 | ---------------- 22 | .. toctree:: 23 | :maxdepth: 2 24 | 25 | modules.rst 26 | 27 | Examples 28 | -------- 29 | .. toctree:: 30 | :maxdepth: 2 31 | 32 | usage.rst 33 | 34 | New Market or Exchange 35 | ---------------------- 36 | .. toctree:: 37 | :maxdepth: 2 38 | 39 | new_market.rst 40 | 41 | Indices and tables 42 | ================== 43 | 44 | * :ref:`genindex` 45 | * :ref:`modindex` 46 | * :ref:`search` 47 | 48 | -------------------------------------------------------------------------------- /tests/test_exchange_calendar_cme_globex_grains.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | from pandas_market_calendars.calendars.cme_globex_agriculture import ( 4 | CMEGlobexGrainsAndOilseedsExchangeCalendar, 5 | ) 6 | 7 | cal = CMEGlobexGrainsAndOilseedsExchangeCalendar() 8 | 9 | 10 | def test_time_zone(): 11 | assert cal.tz == ZoneInfo("America/Chicago") 12 | assert cal.name == "CMEGlobex_GrainsAndOilseeds" 13 | 14 | 15 | def test_x(): 16 | schedule = cal.schedule("2023-01-01", "2023-01-10", tz="America/New_York") 17 | 18 | good_dates = cal.valid_days("2023-01-01", "2023-12-31") 19 | 20 | assert all(d not in good_dates for d in {"2023-01-01", "2023-12-24", "2023-12-25", "2023-12-30", "2023-12-31"}) 21 | 22 | assert all(d in good_dates for d in {"2023-01-03", "2023-01-05", "2023-12-26", "2023-12-27", "2023-12-28"}) 23 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.11" 12 | 13 | # Build documentation in the "docs/" directory with Sphinx 14 | sphinx: 15 | configuration: docs/conf.py 16 | # fail_on_warning: true # Fail on all warnings to avoid broken references 17 | 18 | # Optionally build your docs in additional formats such as PDF and ePub 19 | formats: 20 | - pdf 21 | 22 | # Optional but recommended, declare the Python requirements required 23 | # to build your documentation 24 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 25 | python: 26 | install: 27 | - requirements: docs/requirements.txt 28 | - method: pip 29 | path: . 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 rsheftel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/test_tsx_calendar.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | from pandas.testing import assert_index_equal 5 | 6 | from pandas_market_calendars.calendars.tsx import TSXExchangeCalendar, VictoriaDay 7 | 8 | 9 | def test_time_zone(): 10 | assert TSXExchangeCalendar().tz == ZoneInfo("Canada/Eastern") 11 | assert TSXExchangeCalendar().name == "TSX" 12 | 13 | 14 | def test_victoria_day(): 15 | actual = VictoriaDay.dates("2009-01-01", "2020-12-31") 16 | 17 | expected = pd.DatetimeIndex( 18 | [ 19 | pd.Timestamp("2009-05-18"), 20 | pd.Timestamp("2010-05-24"), 21 | pd.Timestamp("2011-05-23"), 22 | pd.Timestamp("2012-05-21"), 23 | pd.Timestamp("2013-05-20"), 24 | pd.Timestamp("2014-05-19"), 25 | pd.Timestamp("2015-05-18"), 26 | pd.Timestamp("2016-05-23"), 27 | pd.Timestamp("2017-05-22"), 28 | pd.Timestamp("2018-05-21"), 29 | pd.Timestamp("2019-05-20"), 30 | pd.Timestamp("2020-05-18"), 31 | ] 32 | ) 33 | 34 | assert_index_equal(actual, expected) 35 | -------------------------------------------------------------------------------- /tests/test_bse_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from zoneinfo import ZoneInfo 3 | 4 | import pandas as pd 5 | import pytest 6 | 7 | from pandas_market_calendars.calendars.bse import BSEExchangeCalendar, BSEClosedDay 8 | 9 | 10 | def test_time_zone(): 11 | assert BSEExchangeCalendar().tz == ZoneInfo("Asia/Calcutta") 12 | assert BSEExchangeCalendar().name == "BSE" 13 | 14 | 15 | def test_holidays(): 16 | bse_calendar = BSEExchangeCalendar() 17 | 18 | trading_days = bse_calendar.valid_days(pd.Timestamp("2004-01-01"), pd.Timestamp("2018-12-31")) 19 | for session_label in BSEClosedDay: 20 | assert session_label not in trading_days 21 | 22 | 23 | def test_open_close_time(): 24 | bse_calendar = BSEExchangeCalendar() 25 | india_time_zone = ZoneInfo("Asia/Calcutta") 26 | 27 | bse_schedule = bse_calendar.schedule( 28 | start_date=datetime.datetime(2015, 1, 14, tzinfo=india_time_zone), 29 | end_date=datetime.datetime(2015, 1, 16, tzinfo=india_time_zone), 30 | ) 31 | 32 | assert bse_calendar.open_at_time( 33 | schedule=bse_schedule, 34 | timestamp=datetime.datetime(2015, 1, 14, 11, 0, tzinfo=india_time_zone), 35 | ) 36 | 37 | with pytest.raises(ValueError): 38 | bse_calendar.open_at_time( 39 | schedule=bse_schedule, 40 | timestamp=datetime.datetime(2015, 1, 9, 12, 0, tzinfo=india_time_zone), 41 | ) 42 | -------------------------------------------------------------------------------- /tests/test_eurex_calendar.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | 5 | from pandas_market_calendars.calendars.eurex import EUREXExchangeCalendar 6 | 7 | 8 | def test_time_zone(): 9 | assert EUREXExchangeCalendar().tz == ZoneInfo("Europe/Berlin") 10 | assert EUREXExchangeCalendar().name == "EUREX" 11 | 12 | 13 | def test_2016_holidays(): 14 | # good friday: 2016-03-25 15 | # May 1st: on a weekend, not rolled forward 16 | # christmas: on a weekend, not rolled forward 17 | # boxing day: 2016-12-26 18 | # new years (observed): 2016-01-01 19 | eurex = EUREXExchangeCalendar() 20 | good_dates = eurex.valid_days("2016-01-01", "2016-12-31") 21 | for date in ["2016-03-25", "2016-01-01", "2016-12-26"]: 22 | assert pd.Timestamp(date, tz="UTC") not in good_dates 23 | for date in ["2016-05-02"]: 24 | assert pd.Timestamp(date, tz="UTC") in good_dates 25 | 26 | 27 | def test_2017_holidays(): 28 | # good friday: 2017-04-14 29 | # May 1st: 2017-05-01 30 | # christmas (observed): 2017-12-25 31 | # new years (observed): on a weekend, not rolled forward 32 | eurex = EUREXExchangeCalendar() 33 | good_dates = eurex.valid_days("2017-01-01", "2017-12-31") 34 | for date in ["2016-04-14", "2017-05-01", "2017-12-25"]: 35 | assert pd.Timestamp(date, tz="UTC") not in good_dates 36 | for date in ["2017-01-02"]: 37 | assert pd.Timestamp(date, tz="UTC") in good_dates 38 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # -- Project information ----------------------------------------------------- 5 | project = "pandas_market_calendars" 6 | copyright = "2016, Ryan Sheftel" 7 | author = "Ryan Sheftel" 8 | 9 | # -- General configuration ------------------------------------------------ 10 | 11 | # Add any Sphinx extension module names here, as strings. They can be 12 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 13 | # ones. 14 | extensions = [ 15 | "sphinx.ext.autodoc", 16 | "sphinx.ext.autosummary", 17 | "sphinx.ext.todo", 18 | ] 19 | 20 | # Add any paths that contain templates here, relative to this directory. 21 | templates_path = ["_templates"] 22 | 23 | # The suffix of source filenames. 24 | source_suffix = ".rst" 25 | 26 | # The master toctree document. 27 | master_doc = "index" 28 | 29 | # generate docstring for __init__ 30 | autoclass_content = "both" 31 | 32 | # List of patterns, relative to source directory, that match files and 33 | # directories to ignore when looking for source files. 34 | exclude_patterns = ["_build", "**.ipynb_checkpoints"] 35 | 36 | # If true, `todo` and `todoList` produce output, else they produce nothing. 37 | todo_include_todos = True 38 | 39 | # -- Options for HTML output ------------------------------------------------- 40 | # The theme to use for HTML and HTML Help pages. See the documentation for 41 | # a list of builtin themes. 42 | 43 | html_theme = "sphinx_rtd_theme" 44 | -------------------------------------------------------------------------------- /pandas_market_calendars/__init__.py: -------------------------------------------------------------------------------- 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 | from importlib import metadata 18 | 19 | from .calendar_registry import get_calendar, get_calendar_names 20 | from .calendar_utils import convert_freq, date_range, merge_schedules, mark_session 21 | 22 | # TODO: is the below needed? Can I replace all the imports on the calendars with ".market_calendar" 23 | from .market_calendar import MarketCalendar 24 | 25 | # if running in development there may not be a package 26 | try: 27 | __version__ = metadata.version("pandas_market_calendars") 28 | except metadata.PackageNotFoundError: 29 | __version__ = "development" 30 | 31 | __all__ = [ 32 | "MarketCalendar", 33 | "get_calendar", 34 | "get_calendar_names", 35 | "merge_schedules", 36 | "date_range", 37 | "mark_session", 38 | "convert_freq", 39 | ] 40 | -------------------------------------------------------------------------------- /tests/test_iex_calendar.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | from zoneinfo import ZoneInfo 3 | 4 | import numpy as np 5 | import pandas as pd 6 | 7 | from pandas_market_calendars.calendars.iex import IEXExchangeCalendar 8 | from pandas_market_calendars.class_registry import ProtectedDict 9 | 10 | iex = IEXExchangeCalendar() 11 | 12 | 13 | def test_time_zone(): 14 | assert iex.tz == ZoneInfo("America/New_York") 15 | assert iex.name == "IEX" 16 | 17 | 18 | def test_open_close(): 19 | assert iex.open_time == time(9, 30, tzinfo=ZoneInfo("America/New_York")) 20 | assert iex.close_time == time(16, tzinfo=ZoneInfo("America/New_York")) 21 | 22 | 23 | def test_calendar_utility(): 24 | assert len(iex.holidays().holidays) > 0 25 | assert isinstance(iex.regular_market_times, ProtectedDict) 26 | 27 | valid_days = iex.valid_days(start_date="2016-12-20", end_date="2017-01-10") 28 | assert isinstance(valid_days, pd.DatetimeIndex) 29 | assert not valid_days.empty 30 | 31 | schedule = iex.schedule(start_date="2015-07-01", end_date="2017-07-10", start="pre", end="post") 32 | assert isinstance(schedule, pd.DataFrame) 33 | assert not schedule.empty 34 | 35 | 36 | def test_trading_days_before_operation(): 37 | trading_days = iex.valid_days(start_date="2000-01-01", end_date="2022-02-23") 38 | assert np.array([~(trading_days <= "2013-08-25")]).any() 39 | 40 | trading_days = iex.date_range_htf("1D", "2000-01-01", "2022-02-23") 41 | assert np.array([~(trading_days <= "2013-08-25")]).any() 42 | 43 | trading_days = iex.date_range_htf("1D", "2000-01-01", "2010-02-23") 44 | assert len(trading_days) == 0 45 | -------------------------------------------------------------------------------- /tests/test_sse_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from zoneinfo import ZoneInfo 3 | 4 | import pandas as pd 5 | 6 | from pandas_market_calendars.calendars.sse import SSEExchangeCalendar 7 | from pandas_market_calendars.holidays.cn import all_holidays 8 | 9 | all_holidays = pd.DatetimeIndex(all_holidays) 10 | 11 | 12 | def test_time_zone(): 13 | assert SSEExchangeCalendar().tz == ZoneInfo("Asia/Shanghai") 14 | assert SSEExchangeCalendar().name == "SSE" 15 | 16 | 17 | def test_all_holidays(): 18 | sse_calendar = SSEExchangeCalendar() 19 | 20 | trading_days = sse_calendar.valid_days(all_holidays.min(), all_holidays.max()) 21 | assert not all_holidays.tz_localize("UTC").isin(trading_days).any() 22 | 23 | holidays = [ 24 | "2019-05-03", 25 | "2020-01-31", 26 | "2021-02-15", 27 | "2022-05-04", 28 | ] 29 | for date in holidays: 30 | assert pd.Timestamp(date, tz="UTC") not in trading_days 31 | 32 | 33 | def test_sse_closes_at_lunch(): 34 | sse_calendar = SSEExchangeCalendar() 35 | sse_schedule = sse_calendar.schedule( 36 | start_date=datetime.datetime(2015, 1, 14, tzinfo=ZoneInfo("Asia/Shanghai")), 37 | end_date=datetime.datetime(2015, 1, 16, tzinfo=ZoneInfo("Asia/Shanghai")), 38 | ) 39 | 40 | assert sse_calendar.open_at_time( 41 | schedule=sse_schedule, 42 | timestamp=datetime.datetime(2015, 1, 14, 11, 0, tzinfo=ZoneInfo("Asia/Shanghai")), 43 | ) 44 | 45 | assert not sse_calendar.open_at_time( 46 | schedule=sse_schedule, 47 | timestamp=datetime.datetime(2015, 1, 14, 12, 0, tzinfo=ZoneInfo("Asia/Shanghai")), 48 | ) 49 | -------------------------------------------------------------------------------- /pandas_market_calendars/holidays/oz.py: -------------------------------------------------------------------------------- 1 | # OZ Holidays 2 | 3 | from pandas import DateOffset, Timestamp 4 | from pandas.tseries.holiday import ( 5 | Holiday, 6 | MO, 7 | next_monday_or_tuesday, 8 | weekend_to_monday, 9 | previous_friday, 10 | ) 11 | 12 | # New Year's Day 13 | OZNewYearsDay = Holiday( 14 | "New Year's Day", 15 | month=1, 16 | day=1, 17 | observance=weekend_to_monday, 18 | ) 19 | 20 | # Australia Day 21 | AustraliaDay = Holiday( 22 | "Australia Day", 23 | month=1, 24 | day=26, 25 | observance=weekend_to_monday, 26 | ) 27 | 28 | # ANZAC Day 29 | AnzacDay = Holiday( 30 | "ANZAC Day", 31 | month=4, 32 | day=25, 33 | ) 34 | 35 | # Queen's Birthday 36 | QueensBirthday = Holiday( 37 | "Queen's Birthday", 38 | month=6, 39 | day=1, 40 | offset=DateOffset(weekday=MO(2)), 41 | ) 42 | 43 | # Christmas 44 | Christmas = Holiday( 45 | "Christmas", 46 | month=12, 47 | day=25, 48 | observance=weekend_to_monday, 49 | ) 50 | 51 | # Boxing day 52 | BoxingDay = Holiday( 53 | "Boxing Day", 54 | month=12, 55 | day=26, 56 | observance=next_monday_or_tuesday, 57 | ) 58 | 59 | # One-off holiday additions and removals in Australia 60 | 61 | UniqueCloses = [] 62 | 63 | # National Day of Mourning for Her Majesty the Queen 64 | UniqueCloses.append(Timestamp("2022-09-22", tz="UTC")) 65 | 66 | 67 | # Early closes 68 | # Christmas Eve 69 | ChristmasEve = Holiday( 70 | "Christmas Eve", 71 | month=12, 72 | day=24, 73 | observance=previous_friday, 74 | ) 75 | 76 | # New Year's Eve 77 | NewYearsEve = Holiday( 78 | "New Year's Eve", 79 | month=12, 80 | day=31, 81 | observance=previous_friday, 82 | ) 83 | -------------------------------------------------------------------------------- /tests/test_cme_agriculture_calendar.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | 5 | from pandas_market_calendars.calendars.cme import CMEAgricultureExchangeCalendar 6 | 7 | 8 | def test_time_zone(): 9 | assert CMEAgricultureExchangeCalendar().tz == ZoneInfo("America/Chicago") 10 | assert CMEAgricultureExchangeCalendar().name == "CME_Agriculture" 11 | 12 | 13 | def test_2020_holidays(): 14 | # martin luthur king: 2020-01-20 15 | # president's day: 2020-02-17 16 | # good friday: 2020-04-10 17 | # memorial day: 2020-05-25 18 | # independence day: 2020-04-02 and 2020-04-03 19 | # labor day: 2020-09-07 20 | # thanksgiving: 2020-11-25, 2020-11-26 21 | # christmas (observed): 2020-12-25, 2020-12-27 22 | # new years (observed): 2021-01-01 23 | # 24 | # These dates should be excluded, but are still in the calendar: 25 | # - 2020-04-02 26 | # - 2020-04-03 27 | # - 2020-11-25 28 | cme = CMEAgricultureExchangeCalendar() 29 | good_dates = cme.valid_days("2020-01-01", "2021-01-10") 30 | for date in [ 31 | "2020-01-20", 32 | "2020-02-17", 33 | "2020-04-10", 34 | "2020-05-25", 35 | "2020-09-07", 36 | "2020-11-26", 37 | "2020-12-25", 38 | "2020-12-27", 39 | "2021-01-01", 40 | ]: 41 | assert pd.Timestamp(date, tz="UTC") not in good_dates 42 | 43 | 44 | def test_dec_jan(): 45 | cme = CMEAgricultureExchangeCalendar() 46 | schedule = cme.schedule("2020-12-30", "2021-01-10") 47 | 48 | assert schedule["market_open"].iloc[0] == pd.Timestamp("2020-12-29 23:01:00", tz="UTC") 49 | assert schedule["market_close"].iloc[6] == pd.Timestamp("2021-01-08 23:00:00", tz="UTC") 50 | -------------------------------------------------------------------------------- /tests/test_hkex_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from zoneinfo import ZoneInfo 3 | 4 | import pandas as pd 5 | 6 | from pandas_market_calendars.calendars.hkex import HKEXExchangeCalendar 7 | 8 | 9 | def test_time_zone(): 10 | assert HKEXExchangeCalendar().tz == ZoneInfo("Asia/Shanghai") 11 | assert HKEXExchangeCalendar().name == "HKEX" 12 | 13 | 14 | def test_2018_holidays(): 15 | hkex = HKEXExchangeCalendar() 16 | trading_days = hkex.valid_days("2018-01-01", "2018-12-31") 17 | holidays = [ 18 | "2018-01-01", 19 | "2018-02-16", 20 | "2018-02-17", 21 | "2018-02-18", 22 | "2018-02-19", 23 | "2018-03-30", 24 | "2018-04-02", 25 | "2018-04-05", 26 | "2018-05-01", 27 | "2018-05-22", 28 | "2018-06-18", 29 | "2018-07-02", 30 | "2018-09-25", 31 | "2018-10-01", 32 | "2018-10-17", 33 | "2018-12-25", 34 | "2018-12-26", 35 | ] 36 | for date in holidays: 37 | assert pd.Timestamp(date, tz="UTC") not in trading_days 38 | for date in ["2018-05-02"]: 39 | assert pd.Timestamp(date, tz="UTC") in trading_days 40 | 41 | 42 | def test_hkex_closes_at_lunch(): 43 | hkex = HKEXExchangeCalendar() 44 | schedule = hkex.schedule( 45 | start_date=datetime.datetime(2015, 1, 14, tzinfo=ZoneInfo("Asia/Shanghai")), 46 | end_date=datetime.datetime(2015, 1, 16, tzinfo=ZoneInfo("Asia/Shanghai")), 47 | ) 48 | 49 | assert hkex.open_at_time( 50 | schedule=schedule, 51 | timestamp=datetime.datetime(2015, 1, 14, 11, 0, tzinfo=ZoneInfo("Asia/Shanghai")), 52 | ) 53 | 54 | assert not hkex.open_at_time( 55 | schedule=schedule, 56 | timestamp=datetime.datetime(2015, 1, 14, 12, 10, tzinfo=ZoneInfo("Asia/Shanghai")), 57 | ) 58 | -------------------------------------------------------------------------------- /.github/workflows/test_runner.yml: -------------------------------------------------------------------------------- 1 | name: test_runner 2 | on: 3 | pull_request: 4 | branches: [ dev ] 5 | workflow_call: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write # Required for pushing commits if needed in future 10 | 11 | jobs: 12 | run_tests: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | os: [ ubuntu-latest, windows-latest, macos-latest ] 17 | python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14' ] 18 | 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | ref: ${{ github.head_ref }} 26 | 27 | - uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: install dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install pytest coveralls 35 | pip install . 36 | 37 | - name: run tests 38 | run: | 39 | coverage run --source=pandas_market_calendars -m pytest tests 40 | coverage lcov -o coverage.lcov 41 | 42 | - name: coveralls parallel 43 | uses: coverallsapp/github-action@v2 44 | with: 45 | github-token: ${{ secrets.github_token }} 46 | flag-name: test_${{ matrix.os }}_${{ matrix.python-version }} 47 | parallel: true 48 | file: coverage.lcov 49 | 50 | report_coverage: 51 | if: ${{ always() }} 52 | needs: [ run_tests ] 53 | runs-on: ubuntu-latest 54 | 55 | steps: 56 | - uses: actions/checkout@v4 57 | 58 | - name: coveralls finalize 59 | uses: coverallsapp/github-action@v2 60 | with: 61 | github-token: ${{ secrets.github_token }} 62 | parallel-finished: true 63 | -------------------------------------------------------------------------------- /tests/test_ice_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_market_calendars.calendars.ice import ICEExchangeCalendar 4 | 5 | 6 | def test_test_name(): 7 | assert ICEExchangeCalendar().name == "ICE" 8 | 9 | 10 | def test_hurricane_sandy_one_day(): 11 | dates_open = ICEExchangeCalendar().valid_days("2012-10-01", "2012-11-01") 12 | 13 | # closed first day of hurricane sandy 14 | assert pd.Timestamp("2012-10-29", tz="UTC") not in dates_open 15 | 16 | # ICE wasn't closed on day 2 of hurricane sandy 17 | assert pd.Timestamp("2012-10-30", tz="UTC") in dates_open 18 | 19 | 20 | def test_2016_holidays(): 21 | # 2016 holidays: 22 | # new years: 2016-01-01 23 | # good friday: 2016-03-25 24 | # christmas (observed): 2016-12-26 25 | 26 | ice = ICEExchangeCalendar() 27 | good_dates = ice.valid_days("2016-01-01", "2016-12-31") 28 | for date in ["2016-01-01", "2016-03-25", "2016-12-26"]: 29 | assert pd.Timestamp(date, tz="UTC") not in good_dates 30 | 31 | 32 | def test_2016_early_closes(): 33 | # 2016 early closes 34 | # mlk: 2016-01-18 35 | # presidents: 2016-02-15 36 | # mem day: 2016-05-30 37 | # independence day: 2016-07-04 38 | # labor: 2016-09-05 39 | # thanksgiving: 2016-11-24 40 | 41 | ice = ICEExchangeCalendar() 42 | schedule = ice.schedule("2016-01-01", "2016-12-31") 43 | early_closes = ice.early_closes(schedule) 44 | for date in [ 45 | "2016-01-18", 46 | "2016-02-15", 47 | "2016-05-30", 48 | "2016-07-04", 49 | "2016-09-05", 50 | "2016-11-24", 51 | ]: 52 | dt = pd.Timestamp(date) 53 | assert dt in early_closes.index 54 | 55 | market_close = schedule.loc[dt].market_close 56 | # all ICE early closes are 1 pm local 57 | assert market_close.tz_convert(ice.tz).hour == 13 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | .__pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | .pypirc 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # IPython Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # Environments 82 | .env 83 | .venv 84 | env/ 85 | venv*/ 86 | env.bak/ 87 | venv.bak/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | 95 | # Pycharm files 96 | .idea/ 97 | .aiassistant/ 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | # mypy 103 | .mypy_cache/ 104 | 105 | # VSCode 106 | .vscode/* 107 | *.code-workspace 108 | .history/ 109 | 110 | # uv 111 | uv.lock 112 | -------------------------------------------------------------------------------- /tests/test_xtae_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas.testing import assert_index_equal, assert_series_equal 3 | 4 | import pandas_market_calendars as mcal 5 | 6 | 7 | def test_xtae_schedule(): 8 | actual = mcal.get_calendar("XTAE").schedule("2012-07-01", "2012-07-10").index 9 | 10 | expected = pd.DatetimeIndex( 11 | [ 12 | pd.Timestamp("2012-07-01"), 13 | pd.Timestamp("2012-07-02"), 14 | pd.Timestamp("2012-07-03"), 15 | pd.Timestamp("2012-07-04"), 16 | pd.Timestamp("2012-07-05"), 17 | pd.Timestamp("2012-07-08"), 18 | pd.Timestamp("2012-07-09"), 19 | pd.Timestamp("2012-07-10"), 20 | ] 21 | ) 22 | 23 | assert_index_equal(actual, expected) 24 | 25 | 26 | def test_xtae_sunday_close(): 27 | actual = mcal.get_calendar("XTAE").schedule("2012-07-01", "2012-07-10") 28 | 29 | expected = pd.Series( 30 | index=[ 31 | pd.Timestamp("2012-07-01"), 32 | pd.Timestamp("2012-07-02"), 33 | pd.Timestamp("2012-07-03"), 34 | pd.Timestamp("2012-07-04"), 35 | pd.Timestamp("2012-07-05"), 36 | pd.Timestamp("2012-07-08"), 37 | pd.Timestamp("2012-07-09"), 38 | pd.Timestamp("2012-07-10"), 39 | ], 40 | data=[ 41 | pd.Timestamp("2012-07-01 12:40:00+00:00"), 42 | pd.Timestamp("2012-07-02 14:15:00+00:00"), 43 | pd.Timestamp("2012-07-03 14:15:00+00:00"), 44 | pd.Timestamp("2012-07-04 14:15:00+00:00"), 45 | pd.Timestamp("2012-07-05 14:15:00+00:00"), 46 | pd.Timestamp("2012-07-08 12:40:00+00:00"), 47 | pd.Timestamp("2012-07-09 14:15:00+00:00"), 48 | pd.Timestamp("2012-07-10 14:15:00+00:00"), 49 | ], 50 | name="market_close", 51 | ) 52 | 53 | assert_series_equal(actual["market_close"], expected) 54 | -------------------------------------------------------------------------------- /tests/test_cme_equity_calendar.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | 5 | from pandas_market_calendars.calendars.cme import CMEEquityExchangeCalendar 6 | 7 | 8 | def test_time_zone(): 9 | assert CMEEquityExchangeCalendar().tz == ZoneInfo("America/Chicago") 10 | assert CMEEquityExchangeCalendar().name == "CME_Equity" 11 | 12 | 13 | def test_sunday_opens(): 14 | cme = CMEEquityExchangeCalendar() 15 | schedule = cme.schedule("2020-01-01", "2020-01-31", tz="America/New_York") 16 | assert pd.Timestamp("2020-01-12 18:00:00", tz="America/New_York") == schedule.loc["2020-01-13", "market_open"] 17 | 18 | 19 | def test_2016_holidays(): 20 | # good friday: 2016-03-25 21 | # christmas (observed): 2016-12-26 22 | # new years (observed): 2016-01-02 23 | cme = CMEEquityExchangeCalendar() 24 | good_dates = cme.valid_days("2016-01-01", "2016-12-31") 25 | for date in ["2016-03-25", "2016-12-26", "2016-01-02"]: 26 | assert pd.Timestamp(date, tz="UTC") not in good_dates 27 | 28 | 29 | def test_2016_early_closes(): 30 | # mlk day: 2016-01-18 31 | # presidents: 2016-02-15 32 | # mem day: 2016-05-30 33 | # july 4: 2016-07-04 34 | # labor day: 2016-09-05 35 | # thanksgiving: 2016-11-24 36 | 37 | cme = CMEEquityExchangeCalendar() 38 | schedule = cme.schedule("2016-01-01", "2016-12-31") 39 | early_closes = cme.early_closes(schedule).index 40 | 41 | for date in [ 42 | "2016-01-18", 43 | "2016-02-15", 44 | "2016-05-30", 45 | "2016-07-04", 46 | "2016-09-05", 47 | "2016-11-24", 48 | ]: 49 | dt = pd.Timestamp(date) 50 | assert dt in early_closes 51 | 52 | market_close = schedule.loc[dt].market_close 53 | assert market_close.tz_convert(cme.tz).hour == 12 54 | 55 | 56 | def test_dec_jan(): 57 | cme = CMEEquityExchangeCalendar() 58 | schedule = cme.schedule("2016-12-30", "2017-01-10") 59 | 60 | assert schedule["market_open"].iloc[0] == pd.Timestamp("2016-12-29 23:00:00", tz="UTC") 61 | assert schedule["market_close"].iloc[6] == pd.Timestamp("2017-01-10 22:00:00", tz="UTC") 62 | -------------------------------------------------------------------------------- /tests/test_bmf_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from zoneinfo import ZoneInfo 3 | 4 | import pandas as pd 5 | 6 | from pandas_market_calendars.calendars.bmf import BMFExchangeCalendar 7 | 8 | 9 | def test_time_zone(): 10 | assert BMFExchangeCalendar().tz == ZoneInfo("America/Sao_Paulo") 11 | 12 | 13 | def test_2020_holidays_skip(): 14 | # 2020-07-09 - skipped due to covid 15 | # 2020-11-20 - skipped due to covid 16 | 17 | holidays = BMFExchangeCalendar().holidays().holidays 18 | for date in ["2019-07-09", "2019-11-20", "2021-07-09", "2021-11-20"]: 19 | assert pd.Timestamp(date, tz="UTC").to_datetime64() in holidays 20 | for date in ["2020-07-09", "2020-11-20"]: 21 | assert pd.Timestamp(date, tz="UTC").to_datetime64() not in holidays 22 | 23 | 24 | def test_post_2022_regulation_change(): 25 | # Regional holidays no longer observed: January 25th, July 9th, November 20th 26 | # November 20th was reinstated as a national holiday starting in 2024 27 | 28 | holidays = BMFExchangeCalendar().holidays().holidays 29 | 30 | for year in [2017, 2018, 2019, 2021]: # skip 2020 due to test above 31 | for month, day in [(1, 25), (7, 9), (11, 20)]: 32 | assert pd.Timestamp(datetime.date(year, month, day), tz="UTC").to_datetime64() in holidays 33 | for year in range(2022, 2040): 34 | for month, day in [(1, 25), (7, 9)]: 35 | assert pd.Timestamp(datetime.date(year, month, day), tz="UTC").to_datetime64() not in holidays 36 | for year in range(2022, 2024): 37 | for month, day in [(11, 20)]: 38 | assert pd.Timestamp(datetime.date(year, month, day), tz="UTC").to_datetime64() not in holidays 39 | 40 | 41 | def test_sunday_new_years_eve(): 42 | # All instances of December 29th on a Friday should be holidays 43 | 44 | holidays = BMFExchangeCalendar().holidays().holidays 45 | 46 | for year in range(1994, 2040): 47 | date = pd.Timestamp(datetime.date(year, 12, 29), tz="UTC") 48 | if date.day_of_week == 4: 49 | # December 29th on a Friday 50 | 51 | assert date.to_datetime64() in holidays 52 | 53 | 54 | def test_post_2022_nov20(): 55 | # November 20th national holiday should be present from 2024 56 | 57 | holidays = BMFExchangeCalendar().holidays().holidays 58 | 59 | for year in range(2024, 2040): 60 | assert pd.Timestamp(datetime.date(year, 11, 20), tz="UTC").to_datetime64() in holidays 61 | -------------------------------------------------------------------------------- /tests/test_asx_calendar.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | from zoneinfo import ZoneInfo 3 | 4 | import pandas as pd 5 | 6 | from pandas_market_calendars.calendars.asx import ASXExchangeCalendar 7 | 8 | 9 | def test_time_zone(): 10 | assert ASXExchangeCalendar().tz == ZoneInfo("Australia/Sydney") 11 | 12 | 13 | def test_2019_holidays(): 14 | # 2019/01/28 - Australia Day (additional day) 15 | asx = ASXExchangeCalendar() 16 | good_dates = asx.valid_days("2019-01-01", "2019-12-31") 17 | for date in ["2019-01-28"]: 18 | assert pd.Timestamp(date, tz="UTC") not in good_dates 19 | 20 | 21 | def test_2021_holidays(): 22 | # 2021/01/26 - Australia Day 23 | # 2021/12/27 - Christmas (additional day) 24 | # 2021/12/28 - Boxing Day (additional day) 25 | asx = ASXExchangeCalendar() 26 | good_dates = asx.valid_days("2021-01-01", "2021-12-31") 27 | for date in ["2021-01-26", "2021-12-27", "2021-12-28"]: 28 | assert pd.Timestamp(date, tz="UTC") not in good_dates 29 | 30 | 31 | def test_2022_holidays(): 32 | # 2022/01/26 - Australia Day 33 | # 2022/12/25 - Christmas 34 | # 2022/12/26 - Boxing Day 35 | asx = ASXExchangeCalendar() 36 | good_dates = asx.valid_days("2022-01-01", "2022-12-31") 37 | for date in ["2022-01-26", "2022-12-25", "2022-12-26"]: 38 | assert pd.Timestamp(date, tz="UTC") not in good_dates 39 | 40 | 41 | def test_unique_holidays(): 42 | australia_unique_hols_names = ["QEII_DayOfMourning"] 43 | australia_unique_hols = {i: {"closed": None, "open": None} for i in australia_unique_hols_names} 44 | 45 | # One-off holiday additions and removals in Australia 46 | 47 | # National Day of Mourning for Her Majesty the Queen 48 | australia_unique_hols["QEII_DayOfMourning"]["closed"] = [pd.Timestamp("2022-09-22")] 49 | 50 | # Test of closed dates 51 | asx = ASXExchangeCalendar() 52 | # get all the closed dates 53 | closed_days = [australia_unique_hols[k].get("closed") for k in australia_unique_hols] 54 | good_dates = asx.valid_days("1990-01-01", "2022-12-31") 55 | for date in chain.from_iterable(closed_days): 56 | assert pd.Timestamp(date, tz="UTC") not in good_dates 57 | 58 | # Test of open dates 59 | open_days = [australia_unique_hols[k].get("open") for k in australia_unique_hols] 60 | open_days = [i for i in open_days if i] 61 | good_dates = asx.valid_days("1990-01-01", "2022-12-31") 62 | for date in chain.from_iterable(open_days): 63 | assert pd.Timestamp(date, tz="UTC") in good_dates 64 | -------------------------------------------------------------------------------- /tests/test_cme_bond_calendar.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | 5 | from pandas_market_calendars.calendars.cme import CMEBondExchangeCalendar 6 | 7 | 8 | def test_time_zone(): 9 | assert CMEBondExchangeCalendar().tz == ZoneInfo("America/Chicago") 10 | assert CMEBondExchangeCalendar().name == "CME_Bond" 11 | 12 | 13 | def test_sunday_opens(): 14 | cme = CMEBondExchangeCalendar() 15 | schedule = cme.schedule("2020-01-01", "2020-01-31", tz="America/Chicago") 16 | assert pd.Timestamp("2020-01-12 17:00:00", tz="America/Chicago") == schedule.loc["2020-01-13", "market_open"] 17 | 18 | 19 | def test_2020_full_holidays(): 20 | # good friday: 2020-04-10 21 | # new years (observed): 2016-01-01 22 | # christmas (observed): 2020-12-25 23 | cme = CMEBondExchangeCalendar() 24 | good_dates = cme.valid_days("2020-01-01", "2020-12-31") 25 | for date in ["2020-04-10", "2020-01-01", "2020-12-25"]: 26 | assert pd.Timestamp(date, tz="UTC") not in good_dates 27 | 28 | 29 | def test_2020_noon_holidays(): 30 | # MLK: 2020-01-20 31 | # Presidents Day: 2020-02-17 32 | # Memorial Day: 2020-05-25 33 | # Labor Day: 2020-09-07 34 | # Thanksgiving: 2020-11-26 35 | cme = CMEBondExchangeCalendar() 36 | schedule = cme.schedule("2020-01-01", "2020-12-31") 37 | for date in ["2020-01-20", "2020-02-17", "2020-05-25", "2020-09-07", "2020-11-26"]: 38 | assert schedule.loc[date, "market_close"] == pd.Timestamp(date, tz="America/Chicago").replace( 39 | hour=12 40 | ).tz_convert("UTC") 41 | 42 | 43 | def test_2020_noon_15_holidays(): 44 | # Black Friday: 2020-11-27 45 | # Christmas Eve: 2020-12-24 46 | cme = CMEBondExchangeCalendar() 47 | schedule = cme.schedule("2020-11-27", "2020-12-24") 48 | for date in ["2020-11-27", "2020-12-24"]: 49 | assert schedule.loc[date, "market_close"] == pd.Timestamp(date, tz="America/Chicago").replace( 50 | hour=12, minute=15 51 | ).tz_convert("UTC") 52 | 53 | 54 | def test_good_fridays(): 55 | cme = CMEBondExchangeCalendar() 56 | schedule = cme.schedule("2020-01-01", "2021-12-31") 57 | assert pd.Timestamp("2020-04-10") not in schedule.index 58 | 59 | # Good Friday when it is the first friday of the month, open with early close 60 | assert pd.Timestamp("2021-04-02") in schedule.index 61 | assert schedule.loc[pd.Timestamp("2021-04-02"), "market_close"] == pd.Timestamp( 62 | "2021-04-02", tz="America/Chicago" 63 | ).replace(hour=10, minute=00).tz_convert("UTC") 64 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/ice.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | from itertools import chain 4 | 5 | from pandas import Timestamp 6 | from pandas.tseries.holiday import ( 7 | AbstractHolidayCalendar, 8 | GoodFriday, 9 | USLaborDay, 10 | USPresidentsDay, 11 | USThanksgivingDay, 12 | ) 13 | 14 | # check python versiOn aNd import accordingly 15 | if sys.version_info >= (3, 9): 16 | # For Python 3.9 and later, import directly 17 | from zoneinfo import ZoneInfo 18 | else: 19 | # For Python 3.8 and earlier, import from backports 20 | from backports.zoneinfo import ZoneInfo 21 | 22 | from pandas_market_calendars.holidays.us import ( 23 | Christmas, 24 | USIndependenceDay, 25 | USMartinLutherKingJrAfter1998, 26 | USMemorialDay, 27 | USNationalDaysofMourning, 28 | USNewYearsDay, 29 | ) 30 | from pandas_market_calendars.market_calendar import MarketCalendar 31 | 32 | 33 | class ICEExchangeCalendar(MarketCalendar): 34 | """ 35 | Exchange calendar for ICE US. 36 | 37 | Open Time: 8pm, US/Eastern 38 | Close Time: 6pm, US/Eastern 39 | 40 | https://www.theice.com/publicdocs/futures_us/ICE_Futures_US_Regular_Trading_Hours.pdf # noqa 41 | """ 42 | 43 | aliases = ["ICE", "ICEUS", "NYFE"] 44 | regular_market_times = { 45 | "market_open": ((None, time(20, 1), -1),), # offset by -1 day 46 | "market_close": ((None, time(18)),), 47 | } 48 | 49 | @property 50 | def name(self): 51 | return "ICE" 52 | 53 | @property 54 | def tz(self): 55 | return ZoneInfo("US/Eastern") 56 | 57 | @property 58 | def special_closes(self): 59 | return [ 60 | ( 61 | time(13), 62 | AbstractHolidayCalendar( 63 | rules=[ 64 | USMartinLutherKingJrAfter1998, 65 | USPresidentsDay, 66 | USMemorialDay, 67 | USIndependenceDay, 68 | USLaborDay, 69 | USThanksgivingDay, 70 | ] 71 | ), 72 | ) 73 | ] 74 | 75 | @property 76 | def adhoc_holidays(self): 77 | return list( 78 | chain( 79 | USNationalDaysofMourning, 80 | # ICE was only closed on the first day of the Hurricane Sandy 81 | # closings (was not closed on 2012-10-30) 82 | [Timestamp("2012-10-29", tz="UTC")], 83 | ) 84 | ) 85 | 86 | @property 87 | def regular_holidays(self): 88 | # https://www.theice.com/publicdocs/futures_us/exchange_notices/NewExNot2016Holidays.pdf # noqa 89 | return AbstractHolidayCalendar(rules=[USNewYearsDay, GoodFriday, Christmas]) 90 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendar_registry.py: -------------------------------------------------------------------------------- 1 | # fmt: off 2 | # @formatter:off 3 | from .market_calendar import MarketCalendar 4 | from .calendars.asx import ASXExchangeCalendar 5 | from .calendars.bmf import BMFExchangeCalendar 6 | from .calendars.bse import BSEExchangeCalendar 7 | from .calendars.cboe import CFEExchangeCalendar 8 | from .calendars.cme import CMEEquityExchangeCalendar, CMEBondExchangeCalendar 9 | from .calendars.cme_globex_base import CMEGlobexBaseExchangeCalendar 10 | from .calendars.cme_globex_agriculture import ( 11 | CMEGlobexAgricultureExchangeCalendar, 12 | CMEGlobexLivestockExchangeCalendar, 13 | CMEGlobexGrainsAndOilseedsExchangeCalendar, 14 | ) 15 | from .calendars.cme_globex_crypto import CMEGlobexCryptoExchangeCalendar 16 | from .calendars.cme_globex_energy_and_metals import ( 17 | CMEGlobexEnergyAndMetalsExchangeCalendar, 18 | ) 19 | from .calendars.cme_globex_equities import CMEGlobexEquitiesExchangeCalendar 20 | from .calendars.cme_globex_fx import CMEGlobexFXExchangeCalendar 21 | from .calendars.cme_globex_fixed_income import CMEGlobexFixedIncomeCalendar 22 | from .calendars.eurex import EUREXExchangeCalendar 23 | from .calendars.eurex_fixed_income import EUREXFixedIncomeCalendar 24 | from .calendars.hkex import HKEXExchangeCalendar 25 | from .calendars.ice import ICEExchangeCalendar 26 | from .calendars.iex import IEXExchangeCalendar 27 | from .calendars.jpx import JPXExchangeCalendar 28 | from .calendars.lse import LSEExchangeCalendar 29 | from .calendars.nyse import NYSEExchangeCalendar 30 | from .calendars.ose import OSEExchangeCalendar 31 | from .calendars.sifma import ( 32 | SIFMAUSExchangeCalendar, 33 | SIFMAUKExchangeCalendar, 34 | SIFMAJPExchangeCalendar, 35 | ) 36 | from .calendars.six import SIXExchangeCalendar 37 | from .calendars.sse import SSEExchangeCalendar 38 | from .calendars.tase import TASEExchangeCalendar 39 | from .calendars.tsx import TSXExchangeCalendar 40 | from .calendars.mirror import * 41 | # @formatter:on 42 | # fmt: on 43 | 44 | 45 | def get_calendar(name, open_time=None, close_time=None) -> MarketCalendar: 46 | """ 47 | Retrieves an instance of an MarketCalendar whose name is given. 48 | 49 | :param name: The name of the MarketCalendar to be retrieved. 50 | :param open_time: Market open time override as datetime.time object. If None then default is used. 51 | :param close_time: Market close time override as datetime.time object. If None then default is used. 52 | :return: MarketCalendar of the desired calendar. 53 | """ 54 | return MarketCalendar.factory(name, open_time=open_time, close_time=close_time) 55 | 56 | 57 | def get_calendar_names(): 58 | """All Market Calendar names and aliases that can be used in "factory" 59 | :return: list(str) 60 | """ 61 | return MarketCalendar.calendar_names() 62 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This document outlines the ways to contribute to `pandas_market_calendars`. This is a fairly small, low-traffic project, so most of the contribution norms (coding style, acceptance criteria) have been 4 | developed ad hoc and this document will not be exhaustive. If you are interested in contributing code or documentation, please take a moment to at least review the license section to understand how 5 | your code will be licensed. 6 | 7 | ## Types of contribution 8 | 9 | ### Bug reports 10 | 11 | Bug reports are an important type of contribution - it's important to get feedback about how the library is failing, and there's no better way to do that than to hear about real-life failure cases. A 12 | good bug report will include: 13 | 14 | 1. A minimal, reproducible example - a small, self-contained script that can reproduce the behavior is the best way to get your bug fixed. For more information and tips on how to structure these, 15 | read [Stack Overflow's guide to creating a minimal, complete, verified example](https://stackoverflow.com/help/mcve). 16 | 17 | 2. The platform and versions of everything involved, at a minimum please include operating system, `python` version and `pandas_market_calendars` version. Instructions on getting your versions: 18 | - `pandas_market_calendars`: `python -c "import pandas_market_calendars; print(pandas_market_calendars.__version__)"` 19 | - `Python`: `python --version` 20 | 21 | 3. A description of the problem - what *is* happening and what *should* happen. 22 | 23 | While pull requests fixing bugs are accepted, they are *not* required - the bug report in itself is a great contribution. 24 | 25 | ### Feature requests 26 | 27 | If you would like to see a new feature in `pandas_market_calendars`, it is probably best to start an issue for discussion rather than taking the time to implement a feature which may or may not be 28 | appropriate for `pandas_market_calendars`'s API. For minor features (ones where you don't have to put a lot of effort into the PR), a pull request is fine but still not necessary. 29 | 30 | ### Pull requests 31 | 32 | Please format all code using black. 33 | 34 | If you create an editable install with the dev extras you can get a pre-commit hook set-up. 35 | 36 | ``` 37 | python -m venv .venv 38 | . .venv/bin/activate 39 | python -m pip install --upgrade pip 40 | pip install -e .[dev] 41 | pre-commit install 42 | ``` 43 | 44 | If you would like to fix something in `pandas_market_calendars` - improvements to documentation, bug fixes, feature implementations, etc - pull requests are welcome! 45 | 46 | All pull requests should be opened against the `dev` branch and should include a clear and concise summary of the changes you made. 47 | 48 | The most important thing to include in your pull request are *tests* - please write one or more tests to cover the behavior you intend your patch to improve. 49 | -------------------------------------------------------------------------------- /docs/new_market.rst: -------------------------------------------------------------------------------- 1 | New Market or Exchange 2 | ====================== 3 | *See examples/usage.ipynb for demonstrations* 4 | 5 | To create a new exchange (or OTC market): 6 | 7 | #. Create a new class that inherits from MarketCalendar 8 | 9 | #. Set the class attribute `aliases: [...]` for accessing the calendar through `mcal.get_calendar` 10 | 11 | #. Create the `regular_market_times` class attribute, meeting these requirements: 12 | 13 | #. It needs to be a dictionary 14 | 15 | #. Each market_time needs one entry 16 | 17 | #. Regular open must be "market_open", regular close must be "market_close". 18 | #. If there is a break, there must be a "break_start" and a "break_end". 19 | #. only ONE break is currently supported. 20 | 21 | #. One tuple for each market_time, containing at least one tuple: 22 | 23 | #. Each nested tuple needs at least two items: `(first_date_used, time[, offset])`. 24 | #. The first tuple's date should be None, marking the start. In every tuple thereafter this is the date when `time` was first used. 25 | #. Optionally (assumed to be zero, when not present), a positive or negative integer, representing an offset in number of days. 26 | #. Dates need to be in ascending order, None coming first. 27 | 28 | 29 | #. Define the following property methods: 30 | 31 | #. name 32 | #. tz (time zone) 33 | 34 | #. Now optionally define any of the following property methods: 35 | 36 | #. Days where the market is fully closed: 37 | 38 | #. regular_holidays - returns an pandas AbstractHolidayCalendar object 39 | #. adhoc_holidays - returns a list of pandas Timestamp of a DatetimeIndex 40 | 41 | #. Days where the market closes early: 42 | 43 | #. special_closes - returns a list of tuples. The tuple is (datetime.time of close, AbstractHolidayCalendar) 44 | #. special_closes_adhoc - returns a list of tuples. The tuple is (datetime.time of close, list of date strings) 45 | 46 | #. Days where the market opens late: 47 | 48 | #. special_opens - returns a list of tuples. The tuple is (datetime.time of open, AbstractHolidayCalendar) 49 | #. special_opens_adhoc - returns a list of tuples. The tuple is (datetime.time of open, list of date strings) 50 | 51 | #. Set special times for any market_time in regular_market_times, by setting a property in this format: 52 | 53 | #. special_{market_time}_adhoc 54 | same format as special_opens_adhoc, which is the same as special_market_open_adhoc 55 | #. special_{market_time} 56 | same format as special_opens, which is the same as special_market_open 57 | 58 | #. Add interruptions: 59 | 60 | #. interruptions - returns a list of tuples. The tuple is (date, start_time, end_time[, start_time2, end_time2, ...]) 61 | 62 | 63 | #. Import your new calendar class in `calendar_registry.py`: 64 | 65 | .. code:: python 66 | 67 | from .exchange_calendar_xxx import XXXExchangeCalendar 68 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pandas_market_calendars" 3 | version = "5.2.2" 4 | authors = [ 5 | { name = "Ryan Sheftel", email = "rsheftel@alumni.upenn.edu" }, 6 | ] 7 | description = "Market and exchange trading calendars for pandas" 8 | readme = "README.rst" 9 | requires-python = ">=3.8" 10 | keywords = ["trading", "exchanges", "markets", "OTC", "datetime", "holiday", "business days"] 11 | license = { text = "MIT" } 12 | classifiers = [ 13 | 'Development Status :: 5 - Production/Stable', 14 | # Indicate who your project is intended for 15 | 'Intended Audience :: Developers', 16 | 'Topic :: Software Development', 17 | # Pick your license as you wish (should match "license" above) 18 | 'License :: OSI Approved :: MIT License', 19 | # Specify the Python versions you support here. 20 | 'Programming Language :: Python :: 3.8', 21 | 'Programming Language :: Python :: 3.9', 22 | 'Programming Language :: Python :: 3.10', 23 | 'Programming Language :: Python :: 3.11', 24 | 'Programming Language :: Python :: 3.12', 25 | 'Programming Language :: Python :: 3.13', 26 | 'Programming Language :: Python :: 3.14', 27 | ] 28 | dependencies = [ 29 | "exchange-calendars>=3.3", 30 | "pandas>=1.1", 31 | ] 32 | 33 | [build-system] 34 | requires = ["setuptools >= 77.0.3"] 35 | build-backend = "setuptools.build_meta" 36 | 37 | [tool.setuptools] 38 | packages = ["pandas_market_calendars", "pandas_market_calendars.calendars", "pandas_market_calendars.holidays"] 39 | 40 | [project.urls] 41 | "Homepage" = "https://github.com/rsheftel/pandas_market_calendars" 42 | "Source" = "https://github.com/rsheftel/pandas_market_calendars" 43 | "Documentation" = "https://pandas-market-calendars.readthedocs.io/en/latest/" 44 | "Changelog" = "https://pandas-market-calendars.readthedocs.io/en/latest/change_log.html" 45 | "Bug Tracker" = "https://github.com/rsheftel/pandas_market_calendars/issues" 46 | 47 | [tool.coverage.run] 48 | branch = true 49 | 50 | [tool.coverage.report] 51 | exclude_also = [ 52 | # Don't complain about missing debug-only code: 53 | "def __repr__", 54 | "if self\\.debug", 55 | # Don't complain if tests don't hit defensive assertion code: 56 | "raise AssertionError", 57 | "raise NotImplementedError", 58 | # Don't complain if non-runnable code isn't run: 59 | "if 0:", 60 | "if __name__ == .__main__.:", 61 | # Don't complain about abstract methods, they aren't run: 62 | "@(abc\\.)?abstractmethod", 63 | ] 64 | 65 | ignore_errors = true 66 | 67 | [tool.black] 68 | line-length = 120 69 | 70 | [tool.isort] 71 | profile = "black" 72 | line_length = 88 73 | skip_gitignore = true 74 | skip_glob = ["tests/data", "profiling"] 75 | known_first_party = ["black", "blib2to3", "blackd", "_black_version"] 76 | 77 | [dependency-groups] 78 | dev = [ 79 | "black>=24.8.0", 80 | "build>=1.2.2.post1", 81 | "pandas-stubs>=2.0.3.230814", 82 | "pytest>=8.3.5", 83 | "twine>=6.1.0", 84 | ] 85 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/eurex_fixed_income.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | 4 | from pandas.tseries.holiday import ( 5 | AbstractHolidayCalendar, 6 | EasterMonday, 7 | GoodFriday, 8 | Holiday, 9 | ) 10 | 11 | # check python versiOn aNd import accordingly 12 | if sys.version_info >= (3, 9): 13 | # For Python 3.9 and later, import directly 14 | from zoneinfo import ZoneInfo 15 | else: 16 | # For Python 3.8 and earlier, import from backports 17 | from backports.zoneinfo import ZoneInfo 18 | 19 | from pandas_market_calendars.market_calendar import ( 20 | FRIDAY, 21 | MONDAY, 22 | MarketCalendar, 23 | THURSDAY, 24 | TUESDAY, 25 | WEDNESDAY, 26 | ) 27 | 28 | # New Year's Day 29 | EUREXNewYearsDay = Holiday( 30 | "New Year's Day", 31 | month=1, 32 | day=1, 33 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 34 | ) 35 | # Early May bank holiday 36 | MayBank = Holiday( 37 | "Early May Bank Holiday", 38 | month=5, 39 | day=1, 40 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 41 | ) 42 | # Christmas Eve 43 | ChristmasEve = Holiday( 44 | "Christmas Eve", 45 | month=12, 46 | day=24, 47 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 48 | ) 49 | # Christmas 50 | Christmas = Holiday( 51 | "Christmas", 52 | month=12, 53 | day=25, 54 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 55 | ) 56 | # Boxing day 57 | BoxingDay = Holiday( 58 | "Boxing Day", 59 | month=12, 60 | day=26, 61 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 62 | ) 63 | # New Year's Eve 64 | NewYearsEve = Holiday( 65 | "New Year's Eve", 66 | month=12, 67 | day=31, 68 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 69 | ) 70 | 71 | 72 | class EUREXFixedIncomeCalendar(MarketCalendar): 73 | """ 74 | Trading calendar available here: 75 | https://www.eurex.com/resource/blob/3378814/910cf372738890f691bc1bfbccfd3aef/data/tradingcalendar_2023_en.pdf 76 | """ 77 | 78 | aliases = ["EUREX_Bond"] 79 | 80 | regular_market_times = { 81 | "market_open": ((None, time(1, 10)), ("2018-12-10", time(8, 0))), 82 | "market_close": ((None, time(22)),), 83 | } 84 | 85 | @property 86 | def name(self): 87 | return "EUREX_Bond" 88 | 89 | @property 90 | def tz(self): 91 | return ZoneInfo("Europe/Berlin") 92 | 93 | @property 94 | def regular_holidays(self): 95 | return AbstractHolidayCalendar( 96 | rules=[ 97 | EUREXNewYearsDay, 98 | GoodFriday, 99 | EasterMonday, 100 | MayBank, 101 | ChristmasEve, 102 | Christmas, 103 | BoxingDay, 104 | NewYearsEve, 105 | ] 106 | ) 107 | -------------------------------------------------------------------------------- /tests/test_cboe_calendars.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_market_calendars.calendars.cboe import ( 4 | CFEExchangeCalendar, 5 | CBOEEquityOptionsExchangeCalendar, 6 | ) 7 | 8 | calendars = (CFEExchangeCalendar, CBOEEquityOptionsExchangeCalendar) 9 | 10 | 11 | def test_open_time_tz(): 12 | for calendar in calendars: 13 | cal = calendar() 14 | assert cal.open_time.tzinfo == cal.tz 15 | 16 | 17 | def test_close_time_tz(): 18 | for calendar in calendars: 19 | cal = calendar() 20 | assert cal.close_time.tzinfo == cal.tz 21 | 22 | 23 | def test_2016_holidays(): 24 | # new years: jan 1 25 | # mlk: jan 18 26 | # presidents: feb 15 27 | # good friday: mar 25 28 | # mem day: may 30 29 | # independence day: july 4 30 | # labor day: sep 5 31 | # thanksgiving day: nov 24 32 | # christmas (observed): dec 26 33 | # new years (observed): jan 2 2017 34 | for calendar in calendars: 35 | cal = calendar() 36 | good_dates = cal.valid_days("2016-01-01", "2016-12-31") 37 | for day in [ 38 | "2016-01-01", 39 | "2016-01-18", 40 | "2016-02-15", 41 | "2016-05-30", 42 | "2016-07-04", 43 | "2016-09-05", 44 | "2016-11-24", 45 | "2016-12-26", 46 | "2017-01-02", 47 | ]: 48 | assert pd.Timestamp(day, tz="UTC") not in good_dates 49 | 50 | 51 | def test_good_friday_rule(): 52 | # Good friday is a holiday unless Christmas Day or New Years Day is on a Friday 53 | for calendar in calendars: 54 | cal = calendar() 55 | valid_days = cal.valid_days("2015-04-01", "2016-04-01") 56 | for day in ["2015-04-03", "2016-03-25"]: 57 | assert day in valid_days 58 | 59 | 60 | def test_2016_early_closes(): 61 | # only early close is day after thanksgiving: nov 25 62 | for calendar in calendars: 63 | cal = calendar() 64 | schedule = cal.schedule("2016-01-01", "2016-12-31") 65 | 66 | dt = pd.Timestamp("2016-11-25") 67 | assert dt in cal.early_closes(schedule).index 68 | 69 | market_close = schedule.loc[dt].market_close 70 | market_close = market_close.tz_convert(cal.tz) 71 | assert market_close.hour == 12 72 | assert market_close.minute == 15 73 | 74 | 75 | def test_adhoc_holidays(): 76 | # hurricane sandy: oct 29 2012, oct 30 2012 77 | # national days of mourning: 78 | # - apr 27 1994 79 | # - june 11 2004 80 | # - jan 2 2007 81 | for calendar in calendars: 82 | cal = calendar() 83 | valid_days = cal.valid_days("1994-01-01", "2012-12-31") 84 | for day in [ 85 | "1994-04-27", 86 | "2004-06-11", 87 | "2007-01-02", 88 | "2012-10-29", 89 | "2012-10-30", 90 | ]: 91 | print(day) 92 | assert day not in valid_days 93 | 94 | 95 | if __name__ == "__main__": 96 | for ref, obj in locals().copy().items(): 97 | if ref.startswith("test_"): 98 | print("running ", ref) 99 | obj() 100 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/asx.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | 4 | from pandas.tseries.holiday import AbstractHolidayCalendar, GoodFriday, EasterMonday 5 | 6 | # check python versiOn aNd import accordingly 7 | if sys.version_info >= (3, 9): 8 | # For Python 3.9 and later, import directly 9 | from zoneinfo import ZoneInfo 10 | else: 11 | # For Python 3.8 and earlier, import from backports 12 | from backports.zoneinfo import ZoneInfo 13 | 14 | from pandas_market_calendars.holidays.oz import * 15 | from pandas_market_calendars.market_calendar import MarketCalendar 16 | 17 | AbstractHolidayCalendar.start_date = "2011-01-01" 18 | 19 | 20 | class ASXExchangeCalendar(MarketCalendar): 21 | """ 22 | Open Time: 10:00 AM, Australia/Sydney 23 | Close Time: 4:10 PM, Australia/Sydney 24 | 25 | https://www.asx.com.au/markets/market-resources/trading-hours-calendar/cash-market-trading-hours/trading-calendar 26 | 27 | Regularly-Observed Holidays: 28 | - New Year's Day (observed on Monday when Jan 1 is a Saturday or Sunday) 29 | - Australia Day (observed on Monday when Jan 26 is a Saturday or Sunday) 30 | - Good Friday (two days before Easter Sunday) 31 | - Easter Monday (the Monday after Easter Sunday) 32 | - ANZAC Day (April 25) 33 | - Queen's Birthday (second Monday in June) 34 | - Christmas Day (December 25, Saturday/Sunday to Monday) 35 | - Boxing Day (December 26, Saturday to Monday, Sunday to Tuesday) 36 | 37 | 38 | Regularly-Observed Early Closes: 39 | - Last Business Day before Christmas Day 40 | - Last Business Day of the Year 41 | 42 | """ 43 | 44 | aliases = ["ASX"] 45 | regular_market_times = { 46 | "market_open": ((None, time(10)),), 47 | "market_close": ((None, time(16, 10)),), 48 | } 49 | 50 | @property 51 | def name(self): 52 | return "ASX" 53 | 54 | @property 55 | def full_name(self): 56 | return "Australian Securities Exchange" 57 | 58 | @property 59 | def tz(self): 60 | return ZoneInfo("Australia/Sydney") 61 | 62 | @property 63 | def regular_holidays(self): 64 | return AbstractHolidayCalendar( 65 | rules=[ 66 | OZNewYearsDay, 67 | AustraliaDay, 68 | AnzacDay, 69 | QueensBirthday, 70 | Christmas, 71 | BoxingDay, 72 | GoodFriday, 73 | EasterMonday, 74 | ] 75 | ) 76 | 77 | @property 78 | def adhoc_holidays(self): 79 | return UniqueCloses 80 | 81 | @property 82 | def special_closes(self): 83 | return [ 84 | ( 85 | time(hour=14, minute=10, tzinfo=self.tz), 86 | AbstractHolidayCalendar( 87 | rules=[ 88 | ChristmasEve, 89 | ] 90 | ), 91 | ), 92 | ( 93 | time(hour=14, minute=10, tzinfo=self.tz), 94 | AbstractHolidayCalendar( 95 | rules=[ 96 | NewYearsEve, 97 | ] 98 | ), 99 | ), 100 | ] 101 | -------------------------------------------------------------------------------- /tests/test_six_calendar.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | 5 | from pandas_market_calendars.calendars.six import SIXExchangeCalendar 6 | 7 | 8 | def test_time_zone(): 9 | assert SIXExchangeCalendar().tz == ZoneInfo("Europe/Zurich") 10 | assert SIXExchangeCalendar().name == "SIX" 11 | 12 | 13 | def test_2018_holidays(): 14 | # good friday: 2018-04-14 15 | # May 1st: 2018-05-01 16 | # christmas (observed): 2018-12-25 17 | # new years (observed): on a weekend, not rolled forward 18 | # https://www.six-group.com/exchanges/download/participants/regulation/trading_guides/trading_calendar_2018.pdf 19 | six = SIXExchangeCalendar() 20 | good_dates = six.valid_days("2018-01-01", "2018-12-31", tz="Europe/Zurich") 21 | 22 | for date in ["2018-05-24", "2018-06-15", "2018-03-23", "2018-12-21", "2018-12-27"]: 23 | assert pd.Timestamp(date, tz="Europe/Berlin") in good_dates 24 | for date in [ 25 | "2018-01-01", 26 | "2018-01-02", 27 | "2018-03-30", 28 | "2018-04-02", 29 | "2018-05-01", 30 | "2018-05-10", 31 | "2018-05-21", 32 | "2018-08-01", 33 | "2018-12-24", 34 | "2018-12-25", 35 | "2018-12-26", 36 | "2018-12-31", 37 | ]: 38 | assert pd.Timestamp(date, tz="Europe/Zurich") not in good_dates 39 | 40 | 41 | def test_eve_day_weekend(): 42 | # christmas eve (observed): on a weekend 43 | # christmas (observed): 2017-12-25 44 | # boxing day (observed): 2017-12-26 45 | # https://www.six-group.com/exchanges/download/participants/regulation/trading_guides/trading_calendar_2017.pdf 46 | six = SIXExchangeCalendar() 47 | good_dates = six.valid_days("2017-12-01", "2017-12-31", tz="Europe/Zurich") 48 | 49 | for date in ["2017-12-22", "2017-12-27"]: 50 | assert pd.Timestamp(date, tz="Europe/Berlin") in good_dates 51 | for date in ["2017-12-24", "2017-12-25", "2017-12-26"]: 52 | assert pd.Timestamp(date, tz="Europe/Zurich") not in good_dates 53 | 54 | 55 | def test_christmas_weekend(): 56 | # christmas eve (observed): on a weekend 57 | # christmas (observed): on a weekend 58 | # boxing day (observed): 2016-12-26 59 | # https://www.six-group.com/exchanges/download/participants/regulation/trading_guides/trading_calendar_2016.pdf 60 | six = SIXExchangeCalendar() 61 | good_dates = six.valid_days("2016-12-01", "2016-12-31", tz="Europe/Zurich") 62 | 63 | for date in ["2016-12-22", "2016-12-23", "2016-12-27"]: 64 | assert pd.Timestamp(date, tz="Europe/Berlin") in good_dates 65 | for date in ["2016-12-24", "2016-12-25", "2016-12-26"]: 66 | assert pd.Timestamp(date, tz="Europe/Zurich") not in good_dates 67 | 68 | 69 | def test_boxing_day_weekend(): 70 | # christmas eve (observed): 2020-12-24 71 | # christmas (observed): 2020-12-25 72 | # boxing day (observed): on a weekend, not rolled forward 73 | # https://www.six-group.com/exchanges/download/participants/regulation/trading_guides/trading_calendar_2020.pdf 74 | six = SIXExchangeCalendar() 75 | good_dates = six.valid_days("2020-12-01", "2020-12-31", tz="Europe/Zurich") 76 | 77 | for date in ["2020-12-22", "2020-12-22", "2020-12-28"]: 78 | assert pd.Timestamp(date, tz="Europe/Berlin") in good_dates 79 | for date in ["2020-12-24", "2020-12-25", "2020-12-26", "2020-12-27"]: 80 | assert pd.Timestamp(date, tz="Europe/Zurich") not in good_dates 81 | -------------------------------------------------------------------------------- /docs/calendars.rst: -------------------------------------------------------------------------------- 1 | Calendar Status 2 | =============== 3 | 4 | Equity Market Calendars 5 | ####################### 6 | ========= ====== ===================== ============ ========== 7 | Type Name Class Unit Tests Creator 8 | ========= ====== ===================== ============ ========== 9 | Exchange NYSE NYSEExchangeCalendar Yes Quantopian 10 | Exchange LSE LSEExchangeCalendar Yes Quantopian 11 | Exchange CME CMEExchangeCalendar Yes Quantopian 12 | Exchange ICE ICEExchangeCalendar Yes Quantopian 13 | Exchange CFE CFEExchangeCalendar Yes Quantopian 14 | Exchange BMF BMFExchangeCalendar Quantopian 15 | Exchange TSX TSXExchangeCalendar Yes Quantopian 16 | Exchange EUREX EUREXExchangeCalendar Yes kewlfft 17 | Exchange JPX JPXExchangeCalendar Yes gabalese 18 | Exchange SIX SIXExchangeCalendar Yes oliverfu89 19 | Exchange OSE OSEExchangeCalendar Yes busteren 20 | Exchange SSE SSEExchangeCalendar Yes keli 21 | Exchange TASE TASEExchangeCalendar gabglus 22 | Exchange HKEX HKEXExchangeCalendar Yes 1dot75cm 23 | Exchange ASX ASXExchangeCalendar pulledlamb 24 | Exchange BSE BSEExchangeCalendar rakesh1988 25 | Exchange IEX IEXExchangeCalendar Yes carterjfulcher 26 | ========= ====== ===================== ============ ========== 27 | 28 | Futures Calendars 29 | ################# 30 | ========== ================= =================================== ============ ============ 31 | Exchange Name Class Unit Tests Creator 32 | ========== ================= =================================== ============ ============ 33 | CME CME_Equity CMEEquityExchangeCalendar Yes rsheftel 34 | CME CME_Bond CMEBondExchangeCalendar Yes rsheftel 35 | CME CME_Agriculture CMEAgriculturalExchangeCalendar Yes lionelyoung 36 | CME CME Globex Crypto CMEGlobexCryptoExchangeCalendar Yes Coinbase Asset Management 37 | CME CMEGlobex_Grains CMEGlobexGrainsExchangeCalendar Yes rundef 38 | EUREX EUREX_Bond EUREXFixedIncomeCalendar Yes rundef 39 | ========== ================= =================================== ============ ============ 40 | 41 | Bond Market Calendars 42 | ##################### 43 | ========== ================ =================================== ============ ============ 44 | Country Name Class Unit Tests Creator 45 | ========== ================ =================================== ============ ============ 46 | US SIFMAUS SIFMAUSExchangeCalendar Yes 47 | UK SIFMAUK SIFMAUKExchangeCalendar Yes 48 | JP SIFMAJP SIFMAJPExchangeCalendar Yes 49 | ========== ================ =================================== ============ ============ 50 | 51 | Exchange Calendars Package 52 | ########################## 53 | pandas_market_calendars now imports and provides access to all the calendars in `exchange_calendars `_ 54 | 55 | Use the ISO code on the trading_calendars page for those calendars. Many of the calendars are duplicated between 56 | the pandas_market_calendars and trading_calendars projects. Use whichever one you prefer. 57 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/eurex.py: -------------------------------------------------------------------------------- 1 | # 2 | # kewlfft 3 | # 4 | 5 | import sys 6 | from datetime import time 7 | 8 | from pandas.tseries.holiday import ( 9 | AbstractHolidayCalendar, 10 | EasterMonday, 11 | GoodFriday, 12 | Holiday, 13 | previous_friday, 14 | ) 15 | 16 | # check python versiOn aNd import accordingly 17 | if sys.version_info >= (3, 9): 18 | # For Python 3.9 and later, import directly 19 | from zoneinfo import ZoneInfo 20 | else: 21 | # For Python 3.8 and earlier, import from backports 22 | from backports.zoneinfo import ZoneInfo 23 | 24 | from pandas_market_calendars.market_calendar import ( 25 | FRIDAY, 26 | MONDAY, 27 | MarketCalendar, 28 | THURSDAY, 29 | TUESDAY, 30 | WEDNESDAY, 31 | ) 32 | 33 | # New Year's Eve 34 | EUREXNewYearsEve = Holiday( 35 | "New Year's Eve", 36 | month=12, 37 | day=31, 38 | observance=previous_friday, 39 | ) 40 | # New Year's Day 41 | EUREXNewYearsDay = Holiday( 42 | "New Year's Day", 43 | month=1, 44 | day=1, 45 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 46 | ) 47 | # Early May bank holiday 48 | MayBank = Holiday( 49 | "Early May Bank Holiday", 50 | month=5, 51 | day=1, 52 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 53 | ) 54 | # Christmas Eve 55 | ChristmasEve = Holiday( 56 | "Christmas Eve", 57 | month=12, 58 | day=24, 59 | observance=previous_friday, 60 | ) 61 | # Christmas 62 | Christmas = Holiday( 63 | "Christmas", 64 | month=12, 65 | day=25, 66 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 67 | ) 68 | # If christmas day is Saturday Monday 27th is a holiday 69 | # If christmas day is sunday the Tuesday 27th is a holiday 70 | WeekendChristmas = Holiday( 71 | "Weekend Christmas", 72 | month=12, 73 | day=27, 74 | days_of_week=(MONDAY, TUESDAY), 75 | ) 76 | # Boxing day 77 | BoxingDay = Holiday( 78 | "Boxing Day", 79 | month=12, 80 | day=26, 81 | ) 82 | # If boxing day is saturday then Monday 28th is a holiday 83 | # If boxing day is sunday then Tuesday 28th is a holiday 84 | WeekendBoxingDay = Holiday( 85 | "Weekend Boxing Day", 86 | month=12, 87 | day=28, 88 | days_of_week=(MONDAY, TUESDAY), 89 | ) 90 | 91 | 92 | class EUREXExchangeCalendar(MarketCalendar): 93 | """ 94 | Exchange calendar for EUREX 95 | 96 | """ 97 | 98 | aliases = ["EUREX"] 99 | regular_market_times = { 100 | "market_open": ((None, time(9)),), 101 | "market_close": ((None, time(17, 30)),), 102 | } 103 | 104 | @property 105 | def name(self): 106 | return "EUREX" 107 | 108 | @property 109 | def tz(self): 110 | return ZoneInfo("Europe/Berlin") 111 | 112 | @property 113 | def regular_holidays(self): 114 | return AbstractHolidayCalendar( 115 | rules=[ 116 | EUREXNewYearsDay, 117 | GoodFriday, 118 | EasterMonday, 119 | MayBank, 120 | Christmas, 121 | WeekendChristmas, 122 | BoxingDay, 123 | WeekendBoxingDay, 124 | ] 125 | ) 126 | 127 | @property 128 | def special_closes(self): 129 | return [ 130 | ( 131 | time(12, 30), 132 | AbstractHolidayCalendar( 133 | rules=[ 134 | ChristmasEve, 135 | EUREXNewYearsEve, 136 | ] 137 | ), 138 | ) 139 | ] 140 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_fx.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | from pandas.tseries.holiday import AbstractHolidayCalendar 4 | 5 | from pandas_market_calendars.calendars.cme_globex_base import ( 6 | CMEGlobexBaseExchangeCalendar, 7 | ) 8 | from pandas_market_calendars.holidays.cme import ( 9 | USMartinLutherKingJrAfter1998Before2022, 10 | USPresidentsDayBefore2022, 11 | GoodFridayBefore2021, 12 | GoodFriday2021, 13 | GoodFriday2022, 14 | GoodFridayAfter2022, 15 | USMemorialDay2021AndPrior, 16 | USIndependenceDayBefore2022, 17 | USLaborDayStarting1887Before2022, 18 | USThanksgivingBefore2022, 19 | USThanksgivingFriday, 20 | ) 21 | from pandas_market_calendars.holidays.us import ( 22 | USNewYearsDay, 23 | ChristmasEveInOrAfter1993, 24 | Christmas, 25 | ) 26 | 27 | _1015 = time(10, 15) 28 | _1200 = time(12, 0) 29 | _1215 = time(12, 15) 30 | 31 | 32 | class CMEGlobexFXExchangeCalendar(CMEGlobexBaseExchangeCalendar): 33 | aliases = ["CME_Currency"] 34 | 35 | # Using CME Globex trading times eg AUD/USD, EUR/GBP, and BRL/USD 36 | # https://www.cmegroup.com/markets/fx/g10/australian-dollar.contractSpecs.html 37 | # https://www.cmegroup.com/markets/fx/cross-rates/euro-fx-british-pound.contractSpecs.html 38 | # https://www.cmegroup.com/markets/fx/emerging-market/brazilian-real.contractSpecs.html 39 | # CME "NZD spot" has its own holiday schedule; this is a niche product (via "FX Link") and is not handled in this 40 | # class; however, its regular hours follow the same schedule (see 41 | # https://www.cmegroup.com/trading/fx/files/fx-product-guide-2021-us.pdf) 42 | regular_market_times = { 43 | "market_open": ((None, time(17), -1),), # offset by -1 day 44 | "market_close": ((None, time(16, 00)),), 45 | } 46 | 47 | aliases = ["CMEGlobex_FX", "CME_FX", "CME_Currency"] 48 | 49 | @property 50 | def name(self): 51 | return "CMEGlobex_FX" 52 | 53 | @property 54 | def regular_holidays(self): 55 | return AbstractHolidayCalendar( 56 | rules=[ 57 | USNewYearsDay, 58 | GoodFridayBefore2021, 59 | GoodFriday2022, 60 | Christmas, 61 | ] 62 | ) 63 | 64 | @property 65 | def special_closes(self): 66 | """ 67 | Accurate 2020-2022 inclusive 68 | TODO - enhance/verify prior to 2020 69 | TODO - Add 2023+ once known 70 | """ 71 | # Source https://www.cmegroup.com/tools-information/holiday-calendar.html 72 | return [ 73 | ( 74 | _1015, 75 | AbstractHolidayCalendar( 76 | rules=[ 77 | GoodFriday2021, 78 | GoodFridayAfter2022, 79 | ] 80 | ), 81 | ), 82 | ( 83 | _1200, 84 | AbstractHolidayCalendar( 85 | rules=[ 86 | USMartinLutherKingJrAfter1998Before2022, 87 | USPresidentsDayBefore2022, 88 | USMemorialDay2021AndPrior, 89 | USIndependenceDayBefore2022, 90 | USLaborDayStarting1887Before2022, 91 | USThanksgivingBefore2022, 92 | ] 93 | ), 94 | ), 95 | ( 96 | _1215, 97 | AbstractHolidayCalendar(rules=[USThanksgivingFriday, ChristmasEveInOrAfter1993]), 98 | ), 99 | ] 100 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/six.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | 4 | from pandas.tseries.holiday import ( 5 | AbstractHolidayCalendar, 6 | Day, 7 | Easter, 8 | EasterMonday, 9 | GoodFriday, 10 | Holiday, 11 | previous_friday, 12 | ) 13 | 14 | # check python versiOn aNd import accordingly 15 | if sys.version_info >= (3, 9): 16 | # For Python 3.9 and later, import directly 17 | from zoneinfo import ZoneInfo 18 | else: 19 | # For Python 3.8 and earlier, import from backports 20 | from backports.zoneinfo import ZoneInfo 21 | 22 | from pandas_market_calendars.market_calendar import ( 23 | FRIDAY, 24 | MONDAY, 25 | MarketCalendar, 26 | THURSDAY, 27 | TUESDAY, 28 | WEDNESDAY, 29 | ) 30 | 31 | # New Year's Eve 32 | NewYearsEve = Holiday( 33 | "New Year's Eve", 34 | month=12, 35 | day=31, 36 | observance=previous_friday, 37 | ) 38 | # New Year's Day 39 | NewYearsDay = Holiday( 40 | "New Year's Day", 41 | month=1, 42 | day=1, 43 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 44 | ) 45 | # Berthold's Day 46 | BertholdsDay = Holiday( 47 | "Berthold's Day", 48 | month=1, 49 | day=2, 50 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 51 | ) 52 | # Early May bank holiday 53 | MayBank = Holiday( 54 | "Early May Bank Holiday", 55 | month=5, 56 | day=1, 57 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 58 | ) 59 | # Ascension Day (Auffahrt) 60 | AscensionDay = Holiday( 61 | "Ascension Day", 62 | month=1, 63 | day=1, 64 | offset=[Easter(), Day(39)], 65 | days_of_week=(THURSDAY,), 66 | ) 67 | # Pentecost Day (Pfingstmontag) 68 | PentecostMonday = Holiday( 69 | "Pentecost Monday", 70 | month=1, 71 | day=1, 72 | offset=[Easter(), Day(50)], 73 | days_of_week=(MONDAY,), 74 | ) 75 | # Swiss National Day 76 | SwissNationalDay = Holiday( 77 | "Swiss National Day", 78 | month=8, 79 | day=1, 80 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 81 | ) 82 | # Christmas Eve 83 | ChristmasEve = Holiday( 84 | "Christmas Eve", 85 | month=12, 86 | day=24, 87 | ) 88 | # Christmas 89 | Christmas = Holiday( 90 | "Christmas", 91 | month=12, 92 | day=25, 93 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 94 | ) 95 | # Boxing day 96 | BoxingDay = Holiday( 97 | "Boxing Day", 98 | month=12, 99 | day=26, 100 | ) 101 | 102 | 103 | class SIXExchangeCalendar(MarketCalendar): 104 | """ 105 | Exchange calendar for SIX 106 | 107 | """ 108 | 109 | aliases = ["SIX"] 110 | regular_market_times = { 111 | "market_open": ((None, time(9)),), 112 | "market_close": ((None, time(17, 30)),), 113 | } 114 | 115 | @property 116 | def name(self): 117 | return "SIX" 118 | 119 | @property 120 | def full_name(self): 121 | return "SIX Swiss Exchange" 122 | 123 | @property 124 | def tz(self): 125 | return ZoneInfo("Europe/Zurich") 126 | 127 | @property 128 | def regular_holidays(self): 129 | return AbstractHolidayCalendar( 130 | rules=[ 131 | NewYearsDay, 132 | BertholdsDay, 133 | GoodFriday, 134 | EasterMonday, 135 | MayBank, 136 | AscensionDay, 137 | PentecostMonday, 138 | SwissNationalDay, 139 | ChristmasEve, 140 | Christmas, 141 | BoxingDay, 142 | NewYearsEve, 143 | ] 144 | ) 145 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/ose.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | 4 | from pandas.tseries.holiday import ( 5 | AbstractHolidayCalendar, 6 | EasterMonday, 7 | GoodFriday, 8 | Holiday, 9 | ) 10 | from pandas.tseries.offsets import Day, Easter 11 | 12 | # check python versiOn aNd import accordingly 13 | if sys.version_info >= (3, 9): 14 | # For Python 3.9 and later, import directly 15 | from zoneinfo import ZoneInfo 16 | else: 17 | # For Python 3.8 and earlier, import from backports 18 | from backports.zoneinfo import ZoneInfo 19 | 20 | from pandas_market_calendars.market_calendar import MarketCalendar 21 | 22 | OSENewYearsDay = Holiday("New Year's Day", month=1, day=1) 23 | 24 | OSEWednesdayBeforeEaster = Holiday("Wednesday before Easter", month=1, day=1, offset=[Easter(), Day(-4)]) 25 | 26 | OSEMaundyThursday = Holiday("Maundy Thursday", month=1, day=1, offset=[Easter(), Day(-3)]) 27 | 28 | OSEGoodFriday = GoodFriday 29 | 30 | OSEEasterMonday = EasterMonday 31 | 32 | OSELabourDay = Holiday("Labour Day", month=5, day=1) 33 | 34 | OSEConstitutionDay = Holiday("Constitution Day", month=5, day=17) 35 | 36 | OSEWhitMonday = Holiday("Whit Monday", month=1, day=1, offset=[Easter(), Day(50)]) 37 | 38 | OSEAscensionDay = Holiday("Ascension Day", month=1, day=1, offset=[Easter(), Day(39)]) 39 | 40 | OSEChristmasEve = Holiday( 41 | "Christmas Eve", 42 | month=12, 43 | day=24, 44 | ) 45 | 46 | OSEChristmasDay = Holiday("Christmas Day", month=12, day=25) 47 | 48 | OSEBoxingDay = Holiday("Boxing Day", month=12, day=26) 49 | 50 | OSENewYearsEve = Holiday("New Year's Eve", month=12, day=31) 51 | 52 | 53 | class OSEExchangeCalendar(MarketCalendar): 54 | """ 55 | Exchange calendar for Oslo Stock Exchange 56 | 57 | Note these dates are only checked against 2017, 2018 and 2019 58 | https://www.oslobors.no/ob_eng/Oslo-Boers/About-Oslo-Boers/Opening-hours 59 | 60 | Opening times for the regular trading of equities (not including closing auction call) 61 | Open Time: 9:00 AM, CEST/EST 62 | Close Time: 4:20 PM, CEST/EST 63 | 64 | Regularly-Observed Holidays (not necessarily in order): 65 | - New Years Day 66 | - Wednesday before Easter (Half trading day) 67 | - Maundy Thursday 68 | - Good Friday 69 | - Easter Monday 70 | - Labour Day 71 | - Ascension Day 72 | - Constitution Day 73 | - Whit Monday 74 | - Christmas Eve 75 | - Christmas Day 76 | - Boxing Day 77 | - New Year's Eve 78 | """ 79 | 80 | aliases = ["OSE"] 81 | regular_market_times = { 82 | "market_open": ((None, time(9)),), 83 | "market_close": ((None, time(16, 20)),), 84 | } 85 | 86 | @property 87 | def name(self): 88 | return "OSE" 89 | 90 | @property 91 | def full_name(self): 92 | return "Oslo Stock Exchange" 93 | 94 | @property 95 | def tz(self): 96 | return ZoneInfo("Europe/Oslo") 97 | 98 | @property 99 | def regular_holidays(self): 100 | return AbstractHolidayCalendar( 101 | rules=[ 102 | OSENewYearsDay, 103 | OSEMaundyThursday, 104 | OSEGoodFriday, 105 | OSEEasterMonday, 106 | OSELabourDay, 107 | OSEConstitutionDay, 108 | OSEWhitMonday, 109 | OSEAscensionDay, 110 | OSEChristmasEve, 111 | OSEChristmasDay, 112 | OSEBoxingDay, 113 | OSENewYearsEve, 114 | ] 115 | ) 116 | 117 | @property 118 | def special_closes(self): 119 | return [ 120 | ( 121 | time(13, 0, tzinfo=self.tz), 122 | AbstractHolidayCalendar(rules=[OSEWednesdayBeforeEaster]), 123 | ) 124 | ] 125 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_base.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Quantopian, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import sys 17 | from abc import ABC, abstractmethod 18 | 19 | from pandas.tseries.holiday import ( 20 | AbstractHolidayCalendar, 21 | GoodFriday, 22 | USLaborDay, 23 | USPresidentsDay, 24 | USThanksgivingDay, 25 | ) 26 | 27 | # check python versiOn aNd import accordingly 28 | if sys.version_info >= (3, 9): 29 | # For Python 3.9 and later, import directly 30 | from zoneinfo import ZoneInfo 31 | else: 32 | # For Python 3.8 and earlier, import from backports 33 | from backports.zoneinfo import ZoneInfo 34 | 35 | from pandas_market_calendars.holidays.us import ( 36 | Christmas, 37 | ChristmasEveBefore1993, 38 | ChristmasEveInOrAfter1993, 39 | USBlackFridayInOrAfter1993, 40 | USIndependenceDay, 41 | USMartinLutherKingJrAfter1998, 42 | USMemorialDay, 43 | USJuneteenthAfter2022, 44 | USNewYearsDay, 45 | ) 46 | from pandas_market_calendars.market_calendar import MarketCalendar 47 | 48 | 49 | class CMEGlobexBaseExchangeCalendar(MarketCalendar, ABC): 50 | """ 51 | Base Exchange Calendar for CME. 52 | 53 | CME Markets: https://www.cmegroup.com/markets/agriculture.html#overview 54 | - Agriculture 55 | - Crypto 56 | - Energy 57 | - Equity Index 58 | - FX 59 | - Interest Rates 60 | - Metals 61 | - Options 62 | 63 | Holiays for which entire GLOBEX is closed: 64 | - New Years Day 65 | - Good Friday 66 | - Christmas 67 | 68 | Product Specific Closures: 69 | - MLK Day 70 | - Presidents Day 71 | - Memorial Day 72 | - Juneteenth 73 | - US Independence Day 74 | - US Labor Day 75 | - US Thanksgiving Day 76 | """ 77 | 78 | @property 79 | @abstractmethod 80 | def name(self): 81 | """ 82 | Name of the market 83 | 84 | :return: string name 85 | """ 86 | raise NotImplementedError() 87 | 88 | @property 89 | def tz(self): 90 | return ZoneInfo("America/Chicago") 91 | 92 | @property 93 | def regular_holidays(self): 94 | return AbstractHolidayCalendar( 95 | rules=[ 96 | USNewYearsDay, 97 | GoodFriday, 98 | Christmas, 99 | ] 100 | ) 101 | 102 | # I can't find any reference to these special closings onther than NYSE 103 | # @property 104 | # def adhoc_holidays(self): 105 | # return USNationalDaysofMourning 106 | 107 | @property 108 | def special_closes(self): 109 | return [ 110 | ( 111 | self.special_close_time, 112 | AbstractHolidayCalendar( 113 | rules=[ 114 | USMartinLutherKingJrAfter1998, 115 | USPresidentsDay, 116 | USMemorialDay, 117 | USJuneteenthAfter2022, 118 | USLaborDay, 119 | USIndependenceDay, 120 | USThanksgivingDay, 121 | USBlackFridayInOrAfter1993, 122 | ChristmasEveBefore1993, 123 | ChristmasEveInOrAfter1993, 124 | ] 125 | ), 126 | ) 127 | ] 128 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/lse.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Quantopian, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import sys 17 | from datetime import time 18 | 19 | from pandas.tseries.holiday import AbstractHolidayCalendar, EasterMonday, GoodFriday 20 | 21 | # check python versiOn aNd import accordingly 22 | if sys.version_info >= (3, 9): 23 | # For Python 3.9 and later, import directly 24 | from zoneinfo import ZoneInfo 25 | else: 26 | # For Python 3.8 and earlier, import from backports 27 | from backports.zoneinfo import ZoneInfo 28 | 29 | from pandas_market_calendars.holidays.uk import ( 30 | BoxingDay, 31 | Christmas, 32 | ChristmasEve, 33 | LSENewYearsDay, 34 | LSENewYearsEve, 35 | MayBank_pre_1995, 36 | MayBank_post_1995_pre_2020, 37 | MayBank_post_2020, 38 | SpringBank_pre_2002, 39 | SpringBank_post_2002_pre_2012, 40 | SpringBank_post_2012_pre_2022, 41 | SpringBank_post_2022, 42 | SummerBank, 43 | WeekendBoxingDay, 44 | WeekendChristmas, 45 | UniqueCloses, 46 | ) 47 | from pandas_market_calendars.market_calendar import MarketCalendar 48 | 49 | 50 | class LSEExchangeCalendar(MarketCalendar): 51 | """ 52 | Exchange calendar for the London Stock Exchange 53 | 54 | Open Time: 8:00 AM, GMT 55 | Close Time: 4:30 PM, GMT 56 | 57 | Regularly-Observed Holidays: 58 | - New Years Day (observed on first business day on/after) 59 | - Good Friday 60 | - Easter Monday 61 | - Early May Bank Holiday (first Monday in May) 62 | - Spring Bank Holiday (last Monday in May) 63 | - Summer Bank Holiday (last Monday in August) 64 | - Christmas Day 65 | - Dec. 27th (if Christmas is on a weekend) 66 | - Boxing Day 67 | - Dec. 28th (if Boxing Day is on a weekend) 68 | """ 69 | 70 | aliases = ["LSE"] 71 | regular_market_times = { 72 | "market_open": ((None, time(8)),), 73 | "market_close": ((None, time(16, 30)),), 74 | } 75 | 76 | @property 77 | def name(self): 78 | return "LSE" 79 | 80 | @property 81 | def full_name(self): 82 | return "London Stock Exchange" 83 | 84 | @property 85 | def tz(self): 86 | return ZoneInfo("Europe/London") 87 | 88 | @property 89 | def regular_holidays(self): 90 | return AbstractHolidayCalendar( 91 | rules=[ 92 | LSENewYearsDay, 93 | GoodFriday, 94 | EasterMonday, 95 | MayBank_pre_1995, 96 | MayBank_post_1995_pre_2020, 97 | MayBank_post_2020, 98 | SpringBank_pre_2002, 99 | SpringBank_post_2002_pre_2012, 100 | SpringBank_post_2012_pre_2022, 101 | SpringBank_post_2022, 102 | SummerBank, 103 | Christmas, 104 | WeekendChristmas, 105 | BoxingDay, 106 | WeekendBoxingDay, 107 | ] 108 | ) 109 | 110 | @property 111 | def adhoc_holidays(self): 112 | return UniqueCloses 113 | 114 | @property 115 | def special_closes(self): 116 | return [ 117 | ( 118 | time(12, 30), 119 | AbstractHolidayCalendar( 120 | rules=[ 121 | ChristmasEve, 122 | LSENewYearsEve, 123 | ] 124 | ), 125 | ) 126 | ] 127 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Manual Release to PyPI 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | id-token: write # Required for Trusted Publishing OIDC 8 | contents: write # Added: Enables pushing tags (and branches if needed) 9 | 10 | jobs: 11 | test-and-release: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: true 15 | matrix: 16 | os: [ ubuntu-latest, windows-latest, macos-latest ] 17 | python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] 18 | 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | 30 | - name: Install uv 31 | uses: astral-sh/setup-uv@v7 # Latest as of 2025; pin to specific tag if needed 32 | with: 33 | enable-cache: true # Optional: caches uv packages across runs for faster sync 34 | # cache-dependency-glob: "uv.lock" # Invalidate on lockfile changes 35 | 36 | - name: Install dependencies and project 37 | run: uv sync --all-extras --dev 38 | 39 | - name: Run tests 40 | run: uv run pytest 41 | 42 | - name: Extract package name and version from pyproject.toml 43 | if: matrix.python-version == '3.14' && matrix.os == 'ubuntu-latest' 44 | id: metadata 45 | run: | 46 | PACKAGE_NAME=$(uv run python -c "import tomllib; data = tomllib.load(open('pyproject.toml','rb')); print(data['project']['name'])") 47 | VERSION=$(uv run python -c "import tomllib; data = tomllib.load(open('pyproject.toml','rb')); print(data['project']['version'])") 48 | echo "package_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT 49 | echo "version=$VERSION" >> $GITHUB_OUTPUT 50 | 51 | - name: Check if local version > current PyPI version 52 | if: matrix.python-version == '3.14' && matrix.os == 'ubuntu-latest' 53 | run: | 54 | PACKAGE_NAME="${{ steps.metadata.outputs.package_name }}" 55 | LOCAL_VERSION="${{ steps.metadata.outputs.version }}" 56 | 57 | echo "Package name: $PACKAGE_NAME" 58 | echo "Local version: $LOCAL_VERSION" 59 | 60 | # Fetch latest version from PyPI JSON API 61 | RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://pypi.org/pypi/$PACKAGE_NAME/json) 62 | 63 | if [ "$RESPONSE" -eq 404 ]; then 64 | echo "Package not found on PyPI – assuming first release, proceeding." 65 | exit 0 66 | fi 67 | 68 | if [ "$RESPONSE" -ne 200 ]; then 69 | echo "Failed to query PyPI (HTTP $RESPONSE)" 70 | exit 1 71 | fi 72 | 73 | PYPI_VERSION=$(curl -s https://pypi.org/pypi/$PACKAGE_NAME/json | jq -r '.info.version') 74 | echo "Current PyPI version: $PYPI_VERSION" 75 | 76 | # Compare versions using Python's packaging module 77 | uv run python - < $PYPI_VERSION") 84 | EOF 85 | 86 | - name: Tag the commit 87 | if: matrix.python-version == '3.14' && matrix.os == 'ubuntu-latest' 88 | run: | 89 | git config user.name "github-actions" 90 | git config user.email "github-actions@github.com" 91 | git tag v${{ steps.metadata.outputs.version }} 92 | git push origin v${{ steps.metadata.outputs.version }} 93 | 94 | - name: Build package with uv 95 | if: matrix.python-version == '3.14' && matrix.os == 'ubuntu-latest' 96 | run: uv build 97 | 98 | - name: Publish to PyPI using Trusted Publishing 99 | if: matrix.python-version == '3.14' && matrix.os == 'ubuntu-latest' 100 | run: uv publish 101 | -------------------------------------------------------------------------------- /pandas_market_calendars/class_registry.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from pprint import pformat 3 | 4 | 5 | def _regmeta_instance_factory(cls, name, *args, **kwargs): 6 | """ 7 | :param cls(RegisteryMeta): registration meta class 8 | :param name(str): name of class that needs to be instantiated 9 | :param args(Optional(tuple)): instance positional arguments 10 | :param kwargs(Optional(dict)): instance named arguments 11 | :return: class instance 12 | """ 13 | try: 14 | class_ = cls._regmeta_class_registry[name] 15 | except KeyError: 16 | raise RuntimeError( 17 | "Class {} is not one of the registered classes: {}".format(name, cls._regmeta_class_registry.keys()) 18 | ) 19 | return class_(*args, **kwargs) 20 | 21 | 22 | def _regmeta_register_class(cls, regcls, name): 23 | """ 24 | :param cls(RegisteryMeta): registration base class 25 | :param regcls(class): class to be registered 26 | :param name(str): name of the class to be registered 27 | """ 28 | if hasattr(regcls, "aliases"): 29 | if regcls.aliases: 30 | for alias in regcls.aliases: 31 | cls._regmeta_class_registry[alias] = regcls 32 | else: 33 | cls._regmeta_class_registry[name] = regcls 34 | else: 35 | cls._regmeta_class_registry[name] = regcls 36 | 37 | 38 | class RegisteryMeta(type): 39 | """ 40 | Metaclass used to register all classes inheriting from RegisteryMeta 41 | """ 42 | 43 | def __new__(mcs, name, bases, attr): 44 | cls = super(RegisteryMeta, mcs).__new__(mcs, name, bases, attr) 45 | if not hasattr(cls, "_regmeta_class_registry"): 46 | cls._regmeta_class_registry = {} 47 | cls.factory = classmethod(_regmeta_instance_factory) 48 | 49 | return cls 50 | 51 | def __init__(cls, name, bases, attr): 52 | if not inspect.isabstract(cls): 53 | _regmeta_register_class(cls, cls, name) 54 | for b in bases: 55 | if hasattr(b, "_regmeta_class_registry"): 56 | _regmeta_register_class(b, cls, name) 57 | 58 | super(RegisteryMeta, cls).__init__(name, bases, attr) 59 | 60 | cls.regular_market_times = ProtectedDict(cls.regular_market_times) 61 | cls.open_close_map = ProtectedDict(cls.open_close_map) 62 | 63 | cls.special_market_open = cls.special_opens 64 | cls.special_market_open_adhoc = cls.special_opens_adhoc 65 | 66 | cls.special_market_close = cls.special_closes 67 | cls.special_market_close_adhoc = cls.special_closes_adhoc 68 | 69 | 70 | class ProtectedDict(dict): 71 | def __init__(self, *args, **kwargs): 72 | super().__init__(*args, **kwargs) 73 | # __init__ is bypassed when unpickling, which causes __setitem__ to fail 74 | # without the _INIT_RAN_NORMALLY flag 75 | self._INIT_RAN_NORMALLY = True 76 | 77 | def _set(self, key, value): 78 | return super().__setitem__(key, value) 79 | 80 | def _del(self, key): 81 | return super().__delitem__(key) 82 | 83 | def __setitem__(self, key, value): 84 | if not hasattr(self, "_INIT_RAN_NORMALLY"): 85 | return self._set(key, value) 86 | 87 | raise TypeError( 88 | "You cannot set a value directly, you can change regular_market_times " 89 | "using .change_time, .add_time or .remove_time." 90 | ) 91 | 92 | def __delitem__(self, key): 93 | if not hasattr(self, "_INIT_RAN_NORMALLY"): 94 | return self._del(key) 95 | 96 | raise TypeError( 97 | "You cannot delete an item directly. You can change regular_market_times " 98 | "using .change_time, .add_time or .remove_time" 99 | ) 100 | 101 | def __repr__(self): 102 | return self.__class__.__name__ + "(" + super().__repr__() + ")" 103 | 104 | def __str__(self): 105 | try: 106 | formatted = pformat(dict(self), sort_dicts=False) # sort_dicts apparently not available < python3.8 107 | except TypeError: 108 | formatted = pformat(dict(self)) 109 | 110 | return self.__class__.__name__ + "(\n" + formatted + "\n)" 111 | 112 | def copy(self): 113 | return self.__class__(super().copy()) 114 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_equities.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | 4 | from pandas.tseries.holiday import AbstractHolidayCalendar 5 | 6 | # check python versiOn aNd import accordingly 7 | if sys.version_info >= (3, 9): 8 | # For Python 3.9 and later, import directly 9 | from zoneinfo import ZoneInfo 10 | else: 11 | # For Python 3.8 and earlier, import from backports 12 | from backports.zoneinfo import ZoneInfo 13 | 14 | from pandas_market_calendars.holidays.cme import ( 15 | USMartinLutherKingJrAfter1998Before2015, 16 | USMartinLutherKingJrAfter2015, 17 | USPresidentsDayBefore2015, 18 | USPresidentsDayAfter2015, 19 | GoodFridayBefore2021NotEarlyClose, 20 | GoodFriday2010, 21 | GoodFriday2012, 22 | GoodFriday2015, 23 | GoodFriday2021, 24 | GoodFriday2022, 25 | GoodFridayAfter2022, 26 | USMemorialDay2013AndPrior, 27 | USMemorialDayAfter2013, 28 | USIndependenceDayBefore2022PreviousDay, 29 | USIndependenceDayBefore2014, 30 | USIndependenceDayAfter2014, 31 | USLaborDayStarting1887Before2014, 32 | USLaborDayStarting1887After2014, 33 | USThanksgivingBefore2014, 34 | USThanksgivingAfter2014, 35 | USThanksgivingFriday, 36 | ) 37 | from pandas_market_calendars.holidays.us import ( 38 | USNewYearsDay, 39 | ChristmasEveInOrAfter1993, 40 | Christmas, 41 | USJuneteenthAfter2022, 42 | ) 43 | from .cme_globex_base import CMEGlobexBaseExchangeCalendar 44 | 45 | 46 | class CMEGlobexEquitiesExchangeCalendar(CMEGlobexBaseExchangeCalendar): 47 | aliases = ["CME Globex Equity"] 48 | 49 | regular_market_times = { 50 | "market_open": ((None, time(17), -1),), # offset by -1 day 51 | "market_close": ((None, time(16)),), 52 | } 53 | 54 | @property 55 | def tz(self): 56 | return ZoneInfo("America/Chicago") 57 | 58 | @property 59 | def name(self): 60 | """ 61 | Name of the market 62 | 63 | :return: string name 64 | """ 65 | return "CME Globex Equities" 66 | 67 | @property 68 | def regular_holidays(self): 69 | return AbstractHolidayCalendar( 70 | rules=[ 71 | USNewYearsDay, 72 | GoodFridayBefore2021NotEarlyClose, 73 | GoodFriday2022, 74 | Christmas, 75 | ] 76 | ) 77 | 78 | @property 79 | def special_closes(self): 80 | # Source https://www.cmegroup.com/tools-information/holiday-calendar.html 81 | return [ 82 | ( 83 | time(10, 30), 84 | AbstractHolidayCalendar( 85 | rules=[ 86 | USMartinLutherKingJrAfter1998Before2015, 87 | USPresidentsDayBefore2015, 88 | USMemorialDay2013AndPrior, 89 | USIndependenceDayBefore2014, 90 | USLaborDayStarting1887Before2014, 91 | USThanksgivingBefore2014, 92 | ] 93 | ), 94 | ), 95 | ( 96 | time(12, 15), 97 | AbstractHolidayCalendar( 98 | rules=[ 99 | USIndependenceDayBefore2022PreviousDay, 100 | USThanksgivingFriday, 101 | ChristmasEveInOrAfter1993, 102 | ] 103 | ), 104 | ), 105 | ( 106 | time(12), 107 | AbstractHolidayCalendar( 108 | rules=[ 109 | USMartinLutherKingJrAfter2015, 110 | USPresidentsDayAfter2015, 111 | USMemorialDayAfter2013, 112 | USIndependenceDayAfter2014, 113 | USLaborDayStarting1887After2014, 114 | USThanksgivingAfter2014, 115 | USJuneteenthAfter2022, 116 | ] 117 | ), 118 | ), 119 | ( 120 | time(8, 15), 121 | AbstractHolidayCalendar( 122 | rules=[ 123 | GoodFriday2010, 124 | GoodFriday2012, 125 | GoodFriday2015, 126 | GoodFriday2021, 127 | GoodFridayAfter2022, 128 | ] 129 | ), 130 | ), 131 | ] 132 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/jpx.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | from itertools import chain 4 | 5 | from pandas.tseries.holiday import AbstractHolidayCalendar 6 | 7 | # check python versiOn aNd import accordingly 8 | if sys.version_info >= (3, 9): 9 | # For Python 3.9 and later, import directly 10 | from zoneinfo import ZoneInfo 11 | else: 12 | # For Python 3.8 and earlier, import from backports 13 | from backports.zoneinfo import ZoneInfo 14 | 15 | from pandas_market_calendars.holidays.jp import * 16 | from pandas_market_calendars.holidays.us import USNewYearsDay 17 | from pandas_market_calendars.market_calendar import MarketCalendar 18 | 19 | 20 | # TODO: 21 | # From 1949 to 1972 the TSE was open on all non-holiday Saturdays for a half day 22 | # From 1973 to 1984 the TSE was open on all non-holiday Saturdays except the third Saturday of the month 23 | # need to add support for weekmask to make this work properly 24 | 25 | 26 | class JPXExchangeCalendar(MarketCalendar): 27 | """ 28 | Exchange calendar for JPX 29 | 30 | Open Time: 9:31 AM, Asia/Tokyo 31 | LUNCH BREAK :facepalm: : 11:30 AM - 12:30 PM Asia/Tokyo 32 | Close Time: 3:30 PM, Asia/Tokyo 33 | 34 | Market close of Japan changed from 3:00 PM to 3:30 PM on November 5, 2024 35 | Reference: 36 | https://www.jpx.co.jp/english/equities/trading/domestic/tvdivq0000006blj-att/tradinghours_eg.pdf 37 | """ 38 | 39 | aliases = ["JPX", "XJPX"] 40 | regular_market_times = { 41 | "market_open": ((None, time(9)),), 42 | "market_close": ((None, time(15)), ("2024-11-05", time(15, 30))), 43 | "break_start": ((None, time(11, 30)),), 44 | "break_end": ((None, time(12, 30)),), 45 | } 46 | regular_early_close = time(13) 47 | 48 | @property 49 | def name(self): 50 | return "JPX" 51 | 52 | @property 53 | def full_name(self): 54 | return "Japan Exchange Group" 55 | 56 | @property 57 | def tz(self): 58 | return ZoneInfo("Asia/Tokyo") 59 | 60 | @property 61 | def adhoc_holidays(self): 62 | return list( 63 | chain( 64 | AscensionDays, 65 | MarriageDays, 66 | FuneralShowa, 67 | EnthronementDays, 68 | AutumnalCitizenDates, 69 | NoN225IndexPrices, 70 | EquityTradingSystemFailure, 71 | ) 72 | ) 73 | 74 | @property 75 | def regular_holidays(self): 76 | return AbstractHolidayCalendar( 77 | rules=[ 78 | USNewYearsDay, 79 | JapanNewYearsDay2, 80 | JapanNewYearsDay3, 81 | JapanComingOfAgeDay1951To1973, 82 | JapanComingOfAgeDay1974To1999, 83 | JapanComingOfAgeDay, 84 | JapanNationalFoundationDay1969To1973, 85 | JapanNationalFoundationDay, 86 | JapanEmperorsBirthday, 87 | JapanVernalEquinox, 88 | JapanShowaDayUntil1972, 89 | JapanShowaDay, 90 | JapanConstitutionMemorialDayUntil1972, 91 | JapanConstitutionMemorialDay, 92 | JapanGreeneryDay, 93 | JapanChildrensDayUntil1972, 94 | JapanChildrensDay, 95 | JapanGoldenWeekBonusDay, 96 | JapanMarineDay1996To2002, 97 | JapanMarineDay2003To2019, 98 | JapanMarineDay2020, 99 | JapanMarineDay2021, 100 | JapanMarineDay, 101 | JapanMountainDay2016to2019, 102 | JapanMountainDay2020, 103 | JapanMountainDay2021, 104 | JapanMountainDay2021NextDay, 105 | JapanMountainDay, 106 | JapanRespectForTheAgedDay1966To1972, 107 | JapanRespectForTheAgedDay1973To2002, 108 | JapanRespectForTheAgedDay, 109 | JapanAutumnalEquinox, 110 | JapanHealthAndSportsDay1966To1972, 111 | JapanHealthAndSportsDay1973To1999, 112 | JapanHealthAndSportsDay2000To2019, 113 | JapanSportsDay2020, 114 | JapanSportsDay2021, 115 | JapanSportsDay, 116 | JapanCultureDayUntil1972, 117 | JapanCultureDay, 118 | JapanLaborThanksgivingDayUntil1972, 119 | JapanLaborThanksgivingDay, 120 | JapanEmperorAkahitosBirthday, 121 | JapanDecember29Until1988, 122 | JapanDecember30Until1988, 123 | JapanBeforeNewYearsDay, 124 | ] 125 | ) 126 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cboe.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | from itertools import chain 4 | 5 | import pandas as pd 6 | from pandas.tseries.holiday import ( 7 | AbstractHolidayCalendar, 8 | GoodFriday, 9 | USLaborDay, 10 | USPresidentsDay, 11 | USThanksgivingDay, 12 | Holiday, 13 | ) 14 | 15 | # check python versiOn aNd import accordingly 16 | if sys.version_info >= (3, 9): 17 | # For Python 3.9 and later, import directly 18 | from zoneinfo import ZoneInfo 19 | else: 20 | # For Python 3.8 and earlier, import from backports 21 | from backports.zoneinfo import ZoneInfo 22 | 23 | from pandas_market_calendars.holidays.us import ( 24 | Christmas, 25 | USBlackFridayInOrAfter1993, 26 | USIndependenceDay, 27 | USMartinLutherKingJrAfter1998, 28 | USMemorialDay, 29 | USNewYearsDay, 30 | HurricaneSandyClosings, 31 | USNationalDaysofMourning, 32 | USJuneteenthAfter2022, 33 | ) 34 | from pandas_market_calendars.market_calendar import MarketCalendar 35 | 36 | 37 | def good_friday_unless_christmas_nye_friday(dt): 38 | """ 39 | Good Friday is a valid trading day if Christmas Day or New Years Day fall 40 | on a Friday. 41 | """ 42 | if isinstance(dt, pd.DatetimeIndex): 43 | # Pandas < 2.1.0 will call with an index and fall-back to element by element 44 | # Pandas == 2.1.0 will only call element by element 45 | raise NotImplementedError() 46 | 47 | year = dt.year 48 | christmas_weekday = Christmas.observance(pd.Timestamp(year=year, month=12, day=25)).weekday() 49 | nyd_weekday = USNewYearsDay.observance(pd.Timestamp(year=year, month=1, day=1)).weekday() 50 | if christmas_weekday != 4 and nyd_weekday != 4: 51 | return GoodFriday.dates( 52 | pd.Timestamp(year=year, month=1, day=1), 53 | pd.Timestamp(year=year, month=12, day=31), 54 | )[0] 55 | else: 56 | # Not a holiday so use NaT to ensure it gets removed 57 | return pd.NaT 58 | 59 | 60 | GoodFridayUnlessChristmasNYEFriday = Holiday( 61 | name="Good Friday CFE", 62 | month=1, 63 | day=1, 64 | observance=good_friday_unless_christmas_nye_friday, 65 | ) 66 | 67 | 68 | class CFEExchangeCalendar(MarketCalendar): 69 | """ 70 | Exchange calendar for the CBOE Futures Exchange (CFE). 71 | 72 | http://cfe.cboe.com/aboutcfe/expirationcalendar.aspx 73 | 74 | Open Time: 8:30am, America/Chicago 75 | Close Time: 3:15pm, America/Chicago 76 | 77 | (We are ignoring extended trading hours for now) 78 | """ 79 | 80 | aliases = ["CFE", "CBOE_Futures"] 81 | regular_market_times = { 82 | "market_open": ((None, time(8, 30)),), 83 | "market_close": ((None, time(15, 15)),), 84 | } 85 | 86 | @property 87 | def name(self): 88 | return "CFE" 89 | 90 | @property 91 | def full_name(self): 92 | return "CBOE Futures Exchange" 93 | 94 | @property 95 | def tz(self): 96 | return ZoneInfo("America/Chicago") 97 | 98 | @property 99 | def regular_holidays(self): 100 | return AbstractHolidayCalendar( 101 | rules=[ 102 | USNewYearsDay, 103 | USMartinLutherKingJrAfter1998, 104 | USPresidentsDay, 105 | GoodFridayUnlessChristmasNYEFriday, 106 | USJuneteenthAfter2022, 107 | USIndependenceDay, 108 | USMemorialDay, 109 | USLaborDay, 110 | USThanksgivingDay, 111 | Christmas, 112 | ] 113 | ) 114 | 115 | @property 116 | def special_closes(self): 117 | return [ 118 | ( 119 | time(12, 15), 120 | AbstractHolidayCalendar( 121 | rules=[ 122 | USBlackFridayInOrAfter1993, 123 | ] 124 | ), 125 | ) 126 | ] 127 | 128 | @property 129 | def adhoc_holidays(self): 130 | return list( 131 | chain( 132 | HurricaneSandyClosings, 133 | USNationalDaysofMourning, 134 | ) 135 | ) 136 | 137 | 138 | class CBOEEquityOptionsExchangeCalendar(CFEExchangeCalendar): 139 | name = "CBOE_Equity_Options" 140 | aliases = [name] 141 | regular_market_times = { 142 | "market_open": ((None, time(8, 30)),), 143 | "market_close": ((None, time(15)),), 144 | } 145 | 146 | 147 | class CBOEIndexOptionsExchangeCalendar(CFEExchangeCalendar): 148 | name = "CBOE_Index_Options" 149 | aliases = [name] 150 | regular_market_times = { 151 | "market_open": ((None, time(8, 30)),), 152 | "market_close": ((None, time(15, 15)),), 153 | } 154 | -------------------------------------------------------------------------------- /tests/test_eurex_fixed_income_calendar.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | 5 | from pandas_market_calendars.calendars.eurex_fixed_income import ( 6 | EUREXFixedIncomeCalendar, 7 | ) 8 | 9 | 10 | def test_time_zone(): 11 | assert EUREXFixedIncomeCalendar().tz == ZoneInfo("Europe/Berlin") 12 | assert EUREXFixedIncomeCalendar().name == "EUREX_Bond" 13 | 14 | 15 | def _test_year_holiday(year, bad_dates): 16 | eurex = EUREXFixedIncomeCalendar() 17 | good_dates = eurex.valid_days(f"{year}-01-01", f"{year}-12-31") 18 | 19 | # Make sure holiday dates aren't in the schedule 20 | for date in bad_dates: 21 | assert pd.Timestamp(date, tz="UTC") not in good_dates 22 | 23 | # Make sure all other weekdays are in the schedule 24 | expected_good_dates = [ 25 | d.strftime("%Y-%m-%d") 26 | for d in pd.date_range(f"{year}-01-01", f"{year}-12-31", freq="D") 27 | if d.weekday() < 5 and d.strftime("%Y-%m-%d") not in bad_dates 28 | ] 29 | for date in expected_good_dates: 30 | assert pd.Timestamp(date, tz="UTC") in good_dates 31 | 32 | 33 | def test_2017_holidays(): 34 | """ 35 | Eurex is closed for trading and clearing (exercise, settlement and cash) 36 | in all derivatives: 14 April, 17 April, 1 May, 25 December, 26 December 37 | """ 38 | bad_dates = ["2017-04-14", "2017-04-17", "2017-05-01", "2017-12-25", "2017-12-26"] 39 | _test_year_holiday(2017, bad_dates) 40 | 41 | 42 | def test_2018_holidays(): 43 | """ 44 | Eurex is closed for trading and clearing (exercise, settlement and cash) 45 | in all derivatives: 1 January, 30 March, 2 April, 1 May, 25 December, 26 December 46 | Eurex is closed for trading in all derivatives: 24 December, 31 December 47 | """ 48 | bad_dates = [ 49 | "2018-01-01", 50 | "2018-03-30", 51 | "2018-04-02", 52 | "2018-05-01", 53 | "2018-12-24", 54 | "2018-12-25", 55 | "2018-12-26", 56 | "2018-12-31", 57 | ] 58 | _test_year_holiday(2018, bad_dates) 59 | 60 | 61 | def test_2019_holidays(): 62 | """ 63 | Eurex is closed for trading and clearing (exercise, settlement and cash) 64 | in all derivatives: 1 January, 19 April, 22 April, 1 May, 25 December, 26 December 65 | Eurex is closed for trading in all derivatives: 24 December, 31 December 66 | """ 67 | bad_dates = [ 68 | "2019-01-01", 69 | "2019-04-19", 70 | "2019-04-22", 71 | "2019-05-01", 72 | "2019-12-24", 73 | "2019-12-25", 74 | "2019-12-26", 75 | "2019-12-31", 76 | ] 77 | _test_year_holiday(2019, bad_dates) 78 | 79 | 80 | def test_2020_holidays(): 81 | """ 82 | Eurex is closed for trading and clearing (exercise, settlement and cash) 83 | in all derivatives: 1 January, 10 April, 13 April, 1 May, 25 December 84 | Eurex is closed for trading in all derivatives: 24 December, 31 December 85 | """ 86 | bad_dates = [ 87 | "2020-01-01", 88 | "2020-04-10", 89 | "2020-04-13", 90 | "2020-05-01", 91 | "2020-12-24", 92 | "2020-12-25", 93 | "2020-12-31", 94 | ] 95 | _test_year_holiday(2020, bad_dates) 96 | 97 | 98 | def test_2021_holidays(): 99 | """ 100 | Eurex is closed for trading and clearing (exercise, settlement and cash) 101 | in all derivatives: 1 January, 2 April, 5 April 102 | Eurex is closed for trading in all derivatives: 24 December, 31 December 103 | """ 104 | bad_dates = [ 105 | "2021-01-01", 106 | "2021-04-02", 107 | "2021-04-05", 108 | "2021-05-01", 109 | "2021-12-24", 110 | "2021-12-31", 111 | ] 112 | _test_year_holiday(2021, bad_dates) 113 | 114 | 115 | def test_2022_holidays(): 116 | """ 117 | Eurex is closed for trading and clearing (exercise, settlement and cash) 118 | in all derivatives: 15 April, 18 April, 26 December 119 | """ 120 | bad_dates = ["2022-04-15", "2022-04-18", "2022-12-26"] 121 | _test_year_holiday(2022, bad_dates) 122 | 123 | 124 | def test_2023_holidays(): 125 | """ 126 | Eurex is closed for trading and clearing (exercise, settlement and cash) 127 | in all derivatives: 7 April, 10 April, 1 May, 25 December, 26 December 128 | """ 129 | bad_dates = ["2023-04-07", "2023-04-10", "2023-05-01", "2023-12-25", "2023-12-26"] 130 | _test_year_holiday(2023, bad_dates) 131 | 132 | 133 | def test_2024_holidays(): 134 | bad_dates = [ 135 | "2024-01-01", 136 | "2024-03-29", 137 | "2024-04-01", 138 | "2024-05-01", 139 | "2024-12-24", 140 | "2024-12-25", 141 | "2024-12-26", 142 | "2024-12-31", 143 | ] 144 | _test_year_holiday(2024, bad_dates) 145 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_fixed_income.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | from pandas.tseries.holiday import AbstractHolidayCalendar 4 | 5 | from pandas_market_calendars.holidays.cme import ( 6 | USMartinLutherKingJrAfter1998Before2015, 7 | USMartinLutherKingJrAfter1998Before2016FridayBefore, 8 | USMartinLutherKingJrAfter2015, 9 | USPresidentsDayBefore2016FridayBefore, 10 | USPresidentsDayBefore2015, 11 | USPresidentsDayAfter2015, 12 | GoodFridayBefore2021NotEarlyClose, 13 | GoodFriday2009, 14 | GoodFriday2010, 15 | GoodFriday2012, 16 | GoodFriday2015, 17 | GoodFriday2021, 18 | GoodFriday2022, 19 | GoodFridayAfter2022, 20 | USMemorialDay2013AndPrior, 21 | USMemorialDayAfter2013, 22 | USMemorialDay2015AndPriorFridayBefore, 23 | USIndependenceDayBefore2014, 24 | USIndependenceDayAfter2014, 25 | USLaborDayStarting1887Before2014, 26 | USLaborDayStarting1887Before2015FridayBefore, 27 | USLaborDayStarting1887After2014, 28 | USThanksgivingBefore2014, 29 | USThanksgivingAfter2014, 30 | USThanksgivingFriday, 31 | ) 32 | from pandas_market_calendars.holidays.us import ( 33 | USNewYearsDay, 34 | ChristmasEveInOrAfter1993, 35 | Christmas, 36 | USJuneteenthAfter2022, 37 | ) 38 | from .cme_globex_base import CMEGlobexBaseExchangeCalendar 39 | 40 | 41 | class CMEGlobexFixedIncomeCalendar(CMEGlobexBaseExchangeCalendar): 42 | aliases = ["CME Globex Fixed Income", "CME Globex Interest Rate Products"] 43 | 44 | regular_market_times = { 45 | "market_open": ((None, time(18), -1),), 46 | "market_close": ((None, time(17)),), 47 | } 48 | 49 | """ 50 | Not yet implemented: 51 | Christmas/New_Years 52 | 5am special open for a couple years (see tests) 53 | 54 | regular market_open/market_close changed from 17/16 to 18/17? 55 | """ 56 | 57 | @property 58 | def name(self): 59 | return "CME Globex Fixed Income" 60 | 61 | @property 62 | def regular_holidays(self): 63 | return AbstractHolidayCalendar( 64 | rules=[ 65 | USNewYearsDay, 66 | GoodFridayBefore2021NotEarlyClose, 67 | GoodFriday2022, 68 | Christmas, 69 | ] 70 | ) 71 | 72 | @property 73 | def special_closes_adhoc(self): 74 | return [ 75 | (time(15, 15), ["2010-07-02", "2011-07-01"]), 76 | (time(12, 15), ["2010-12-31"]), 77 | ] 78 | 79 | @property 80 | def special_closes(self): 81 | # Source https://www.cmegroup.com/tools-information/holiday-calendar.html 82 | return [ 83 | ( 84 | time(12), 85 | AbstractHolidayCalendar( 86 | rules=[ 87 | USMartinLutherKingJrAfter1998Before2015, 88 | USMartinLutherKingJrAfter2015, 89 | USPresidentsDayBefore2015, 90 | USPresidentsDayAfter2015, 91 | USMemorialDay2013AndPrior, 92 | USMemorialDayAfter2013, 93 | USIndependenceDayBefore2014, 94 | USIndependenceDayAfter2014, 95 | USLaborDayStarting1887Before2014, 96 | USLaborDayStarting1887After2014, 97 | USThanksgivingBefore2014, 98 | USThanksgivingAfter2014, 99 | USJuneteenthAfter2022, 100 | ] 101 | ), 102 | ), 103 | ( 104 | time(15, 15), 105 | AbstractHolidayCalendar( 106 | rules=[ 107 | USMartinLutherKingJrAfter1998Before2016FridayBefore, 108 | USPresidentsDayBefore2016FridayBefore, 109 | GoodFriday2009, 110 | USMemorialDay2015AndPriorFridayBefore, 111 | USLaborDayStarting1887Before2015FridayBefore, 112 | ] 113 | ), 114 | ), 115 | ( 116 | time(12, 15), 117 | AbstractHolidayCalendar( 118 | rules=[ 119 | USThanksgivingFriday, 120 | ChristmasEveInOrAfter1993, 121 | ] 122 | ), 123 | ), 124 | ( 125 | time(10, 15), 126 | AbstractHolidayCalendar( 127 | rules=[ 128 | GoodFriday2010, 129 | GoodFriday2012, 130 | GoodFriday2015, 131 | GoodFriday2021, 132 | GoodFridayAfter2022, 133 | ] 134 | ), 135 | ), 136 | ] 137 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/mirror.py: -------------------------------------------------------------------------------- 1 | """ 2 | Imported calendars from the exchange_calendars project 3 | 4 | GitHub: https://github.com/gerrymanoim/exchange_calendars 5 | """ 6 | 7 | import exchange_calendars 8 | 9 | from pandas_market_calendars.market_calendar import MarketCalendar 10 | 11 | DAYMASKS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 12 | 13 | 14 | class TradingCalendar(MarketCalendar): 15 | """ 16 | This class provides access to all the information on opens, breaks and closes that are available 17 | in the exchange_calendars package, it will receive the correctly formatted regular_market_times 18 | dictionary in the for-loop below. 19 | 20 | The initialization of calendars from exchange_calendars, is bypassed until the `.ec` property is used, 21 | which returns the initialized exchange_calendar calendar, which is only initialized the first time. 22 | """ 23 | 24 | # flag indicating that offset still needs to be checked. 25 | # A class attribute so we only do this once per class and not per instance 26 | _FINALIZE_TRADING_CALENDAR = True 27 | 28 | def __new__(cls, *args, **kwargs): 29 | self = super().__new__(cls) 30 | self._ec = super().__new__(cls._ec_class) 31 | # flag indicating that mirrored class is not initialized yet, which we only want to do 32 | # once per instance, if and only if the public `.ec` property is used. 33 | self._EC_NOT_INITIALIZED = True 34 | 35 | # offsets of exchange_calendar_mirrors are only available through the instance 36 | if cls._FINALIZE_TRADING_CALENDAR: 37 | if self._ec.open_offset: 38 | cls.regular_market_times._set( 39 | "market_open", 40 | tuple((t[0], t[1], self._ec.open_offset) for t in cls.regular_market_times["market_open"]), 41 | ) 42 | 43 | if self._ec.close_offset: 44 | cls.regular_market_times._set( 45 | "market_close", 46 | tuple((t[0], t[1], self._ec.close_offset) for t in cls.regular_market_times["market_close"]), 47 | ) 48 | cls._FINALIZE_TRADING_CALENDAR = False 49 | 50 | self.__init__(*args, **kwargs) 51 | return self 52 | 53 | def __init__(self, open_time=None, close_time=None): 54 | super().__init__(open_time, close_time) 55 | 56 | @property 57 | def ec(self): 58 | if self._EC_NOT_INITIALIZED: 59 | self._ec.__init__() 60 | self._EC_NOT_INITIALIZED = False 61 | 62 | return self._ec 63 | 64 | @property 65 | def name(self): 66 | return self._ec.name 67 | 68 | @property 69 | def full_name(self): 70 | return self._ec.name 71 | 72 | @property 73 | def tz(self): 74 | return self._ec.tz 75 | 76 | @property 77 | def regular_holidays(self): 78 | return self._ec.regular_holidays 79 | 80 | @property 81 | def adhoc_holidays(self): 82 | return self._ec.adhoc_holidays 83 | 84 | @property 85 | def special_opens(self): 86 | return self._ec.special_opens 87 | 88 | @property 89 | def special_opens_adhoc(self): 90 | return self._ec.special_opens_adhoc 91 | 92 | @property 93 | def special_closes(self): 94 | return self._ec.special_closes 95 | 96 | @property 97 | def special_closes_adhoc(self): 98 | return self._ec.special_closes_adhoc 99 | 100 | @property 101 | def weekmask(self): 102 | if hasattr(self._ec, "weekmask"): 103 | if "1" in self._ec.weekmask or "0" in self._ec.weekmask: 104 | # Convert 1s & 0s to Day Abbreviations 105 | return " ".join([DAYMASKS[i] for i, val in enumerate(self._ec.weekmask) if val == "1"]) 106 | else: 107 | return self._ec.weekmask 108 | else: 109 | return "Mon Tue Wed Thu Fri" 110 | 111 | 112 | calendars = exchange_calendars.calendar_utils._default_calendar_factories # noqa 113 | 114 | time_props = dict( 115 | open_times="market_open", 116 | close_times="market_close", 117 | break_start_times="break_start", 118 | break_end_times="break_end", 119 | ) 120 | 121 | for exchange in calendars: 122 | cal = calendars[exchange] 123 | 124 | # this loop will set up the newly required regular_market_times dictionary 125 | regular_market_times = {} 126 | for prop, new in time_props.items(): 127 | times = getattr(cal, prop) 128 | if times is None or isinstance(times, property): 129 | continue 130 | regular_market_times[new] = times 131 | 132 | cal = type( 133 | exchange, 134 | (TradingCalendar,), 135 | { 136 | "_ec_class": calendars[exchange], 137 | "alias": [exchange], 138 | "regular_market_times": regular_market_times, 139 | }, 140 | ) 141 | locals()[f"{exchange}ExchangeCalendar"] = cal 142 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/tsx.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | from itertools import chain 4 | 5 | import pandas as pd 6 | from pandas.tseries.holiday import ( 7 | AbstractHolidayCalendar, 8 | DateOffset, 9 | GoodFriday, 10 | Holiday, 11 | MO, 12 | weekend_to_monday, 13 | ) 14 | 15 | # check python versiOn aNd import accordingly 16 | if sys.version_info >= (3, 9): 17 | # For Python 3.9 and later, import directly 18 | from zoneinfo import ZoneInfo 19 | else: 20 | # For Python 3.8 and earlier, import from backports 21 | from backports.zoneinfo import ZoneInfo 22 | 23 | from pandas_market_calendars.holidays.uk import ( 24 | BoxingDay, 25 | WeekendBoxingDay, 26 | WeekendChristmas, 27 | ) 28 | from pandas_market_calendars.market_calendar import ( 29 | MarketCalendar, 30 | MONDAY, 31 | TUESDAY, 32 | WEDNESDAY, 33 | THURSDAY, 34 | FRIDAY, 35 | ) 36 | 37 | # New Year's Day 38 | TSXNewYearsDay = Holiday( 39 | "New Year's Day", 40 | month=1, 41 | day=1, 42 | observance=weekend_to_monday, 43 | ) 44 | # Ontario Family Day 45 | FamilyDay = Holiday( 46 | "Family Day", 47 | month=2, 48 | day=1, 49 | offset=DateOffset(weekday=MO(3)), 50 | start_date="2008-01-01", 51 | ) 52 | # Victoria Day 53 | # https://www.timeanddate.com/holidays/canada/victoria-day 54 | VictoriaDay = Holiday( 55 | "Victoria Day", 56 | month=5, 57 | day=24, 58 | offset=DateOffset(weekday=MO(-1)), 59 | ) 60 | # Canada Day 61 | CanadaDay = Holiday( 62 | "Canada Day", 63 | month=7, 64 | day=1, 65 | observance=weekend_to_monday, 66 | ) 67 | # Civic Holiday 68 | CivicHoliday = Holiday( 69 | "Civic Holiday", 70 | month=8, 71 | day=1, 72 | offset=DateOffset(weekday=MO(1)), 73 | ) 74 | # Labor Day 75 | LaborDay = Holiday( 76 | "Labor Day", 77 | month=9, 78 | day=1, 79 | offset=DateOffset(weekday=MO(1)), 80 | ) 81 | # Thanksgiving 82 | Thanksgiving = Holiday( 83 | "Thanksgiving", 84 | month=10, 85 | day=1, 86 | offset=DateOffset(weekday=MO(2)), 87 | ) 88 | 89 | Christmas = Holiday( 90 | "Christmas", 91 | month=12, 92 | day=25, 93 | ) 94 | 95 | ChristmasEveEarlyClose2010Onwards = Holiday( 96 | "Christmas Eve Early Close", 97 | month=12, 98 | day=24, 99 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 100 | start_date=pd.Timestamp("2010-01-01"), 101 | ) 102 | 103 | September11Closings2001 = [ 104 | pd.Timestamp("2001-09-11", tz="UTC"), 105 | pd.Timestamp("2001-09-12", tz="UTC"), 106 | ] 107 | 108 | 109 | class TSXExchangeCalendar(MarketCalendar): 110 | """ 111 | Exchange calendar for the Toronto Stock Exchange 112 | 113 | Open Time: 9:30 AM, EST 114 | Close Time: 4:00 PM, EST 115 | 116 | Regularly-Observed Holidays: 117 | - New Years Day (observed on first business day on/after) 118 | - Family Day (Third Monday in February, starting in 2008) 119 | - Good Friday 120 | - Victoria Day (Monday before May 25th) 121 | - Canada Day (July 1st, observed first business day after) 122 | - Civic Holiday (First Monday in August) 123 | - Labor Day (First Monday in September) 124 | - Thanksgiving (Second Monday in October) 125 | - Christmas Day 126 | - Dec. 26th if Christmas is on a Sunday 127 | - Dec. 27th if Christmas is on a weekend 128 | - Boxing Day 129 | - Dec. 27th if Christmas is on a Sunday 130 | - Dec. 28th if Boxing Day is on a weekend 131 | 132 | Early closes: 133 | - Starting in 2010, if Christmas Eve falls on a weekday, the market 134 | closes at 1:00 pm that day. If it falls on a weekend, there is no 135 | early close. 136 | """ 137 | 138 | aliases = ["TSX", "TSXV"] 139 | 140 | regular_market_times = { 141 | "market_open": ((None, time(9, 30)),), 142 | "market_close": ((None, time(16)),), 143 | } 144 | 145 | @property 146 | def name(self): 147 | return "TSX" 148 | 149 | @property 150 | def full_name(self): 151 | return "Toronto Stock Exchange" 152 | 153 | @property 154 | def tz(self): 155 | return ZoneInfo("Canada/Eastern") 156 | 157 | regular_early_close = time(13) 158 | 159 | @property 160 | def regular_holidays(self): 161 | return AbstractHolidayCalendar( 162 | rules=[ 163 | TSXNewYearsDay, 164 | FamilyDay, 165 | GoodFriday, 166 | VictoriaDay, 167 | CanadaDay, 168 | CivicHoliday, 169 | LaborDay, 170 | Thanksgiving, 171 | Christmas, 172 | WeekendChristmas, 173 | BoxingDay, 174 | WeekendBoxingDay, 175 | ] 176 | ) 177 | 178 | @property 179 | def adhoc_holidays(self): 180 | return list( 181 | chain( 182 | September11Closings2001, 183 | ) 184 | ) 185 | 186 | @property 187 | def special_closes(self): 188 | return [ 189 | ( 190 | self.regular_early_close, 191 | AbstractHolidayCalendar([ChristmasEveEarlyClose2010Onwards]), 192 | ) 193 | ] 194 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/iex.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import time 3 | from itertools import chain 4 | 5 | from pandas import Timestamp, DatetimeIndex, Timedelta 6 | from pandas.tseries.holiday import AbstractHolidayCalendar 7 | 8 | # check python versiOn aNd import accordingly 9 | if sys.version_info >= (3, 9): 10 | # For Python 3.9 and later, import directly 11 | from zoneinfo import ZoneInfo 12 | else: 13 | # For Python 3.8 and earlier, import from backports 14 | from backports.zoneinfo import ZoneInfo 15 | 16 | from typing import Literal, Union 17 | from pandas_market_calendars import calendar_utils as u 18 | 19 | from pandas_market_calendars.holidays.nyse import ( 20 | USPresidentsDay, 21 | GoodFriday, 22 | USMemorialDay, 23 | USJuneteenthAfter2022, 24 | USIndependenceDay, 25 | USThanksgivingDay, 26 | ChristmasNYSE, 27 | USMartinLutherKingJrAfter1998, 28 | # Ad-Hoc 29 | DayAfterThanksgiving1pmEarlyCloseInOrAfter1993, 30 | DaysBeforeIndependenceDay1pmEarlyCloseAdhoc, 31 | ChristmasEvesAdhoc, 32 | ) 33 | from .nyse import NYSEExchangeCalendar 34 | 35 | 36 | class IEXExchangeCalendar(NYSEExchangeCalendar): 37 | """ 38 | Exchange calendar for the Investor's Exchange (IEX). 39 | 40 | IEX Exchange is a U.S. stock exchange focused on driving performance 41 | for broker-dealers and investors through innovative design and technology. 42 | 43 | Most of this class inherits from NYSEExchangeCalendar since 44 | the holidays are the same. The only variation is (1) IEX began 45 | operation in 2013, and (2) IEX has different hours of operation 46 | 47 | References: 48 | - https://exchange.iex.io/ 49 | - https://iexexchange.io/resources/trading/trading-hours-holidays/index.html 50 | """ 51 | 52 | regular_market_times = { 53 | "pre": (("2013-03-25", time(8)),), 54 | "market_open": ((None, time(9, 30)),), 55 | "market_close": ((None, time(16)),), 56 | "post": ((None, time(17)),), 57 | } 58 | 59 | aliases = ["IEX", "Investors_Exchange"] 60 | 61 | @property 62 | def name(self): 63 | return "IEX" 64 | 65 | @property 66 | def full_name(self): 67 | return "Investor's Exchange" 68 | 69 | @property 70 | def weekmask(self): 71 | return "Mon Tue Wed Thu Fri" 72 | 73 | @property 74 | def regular_holidays(self): 75 | return AbstractHolidayCalendar( 76 | rules=[ 77 | USPresidentsDay, 78 | GoodFriday, 79 | USMemorialDay, 80 | USJuneteenthAfter2022, 81 | USIndependenceDay, 82 | USThanksgivingDay, 83 | ChristmasNYSE, 84 | USMartinLutherKingJrAfter1998, 85 | ] 86 | ) 87 | 88 | @property 89 | def adhoc_holidays(self): 90 | return list( 91 | chain( 92 | ChristmasEvesAdhoc, 93 | ) 94 | ) 95 | 96 | @property 97 | def special_closes(self): 98 | return [ 99 | ( 100 | time(hour=13, tzinfo=ZoneInfo("America/New_York")), 101 | AbstractHolidayCalendar( 102 | rules=[ 103 | DayAfterThanksgiving1pmEarlyCloseInOrAfter1993, 104 | ] 105 | ), 106 | ) 107 | ] 108 | 109 | """Override NYSE calendar special cases""" 110 | 111 | @property 112 | def special_closes_adhoc(self): 113 | return [ 114 | ( 115 | time(13, tzinfo=ZoneInfo("America/New_York")), 116 | DaysBeforeIndependenceDay1pmEarlyCloseAdhoc, 117 | ) 118 | ] 119 | 120 | @property 121 | def special_opens(self): 122 | return [] 123 | 124 | def valid_days(self, start_date, end_date, tz="UTC"): 125 | start_date = Timestamp(start_date) 126 | if start_date.tz is not None: 127 | # Ensure valid Comparison to "2013-08-25" is possible 128 | start_date.tz_convert(self.tz).tz_localize(None) 129 | 130 | # Limit Start_date to the Exchange's Open 131 | start_date = max(start_date, Timestamp("2013-08-25")) 132 | return super().valid_days(start_date, end_date, tz=tz) 133 | 134 | def date_range_htf( 135 | self, 136 | frequency: Union[str, Timedelta, int, float], 137 | start: Union[str, Timestamp, int, float, None] = None, 138 | end: Union[str, Timestamp, int, float, None] = None, 139 | periods: Union[int, None] = None, 140 | closed: Union[Literal["left", "right"], None] = "right", 141 | *, 142 | day_anchor: u.Day_Anchor = "SUN", 143 | month_anchor: u.Month_Anchor = "JAN", 144 | ) -> DatetimeIndex: 145 | 146 | start, end, periods = u._error_check_htf_range(start, end, periods) 147 | 148 | # Cap Beginning and end dates to the opening date of IEX 149 | if start is not None: 150 | start = max(start, Timestamp("2013-08-25")) 151 | if end is not None: 152 | end = max(end, Timestamp("2013-08-25")) 153 | 154 | return u.date_range_htf( 155 | self.holidays(), 156 | frequency, 157 | start, 158 | end, 159 | periods, 160 | closed, 161 | day_anchor=day_anchor, 162 | month_anchor=month_anchor, 163 | ) 164 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_agriculture.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Quantopian, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from abc import abstractmethod 17 | from datetime import time 18 | 19 | from pandas.tseries.holiday import ( 20 | AbstractHolidayCalendar, 21 | GoodFriday, 22 | USLaborDay, 23 | USPresidentsDay, 24 | USThanksgivingDay, 25 | ) 26 | 27 | from pandas_market_calendars.holidays.us import ( 28 | Christmas, 29 | ChristmasEveBefore1993, 30 | ChristmasEveInOrAfter1993, 31 | USBlackFridayInOrAfter1993, 32 | USIndependenceDay, 33 | USMartinLutherKingJrAfter1998, 34 | USMemorialDay, 35 | USNewYearsDay, 36 | ) 37 | from .cme_globex_base import CMEGlobexBaseExchangeCalendar 38 | 39 | 40 | class CMEGlobexAgricultureExchangeCalendar(CMEGlobexBaseExchangeCalendar): 41 | """ 42 | Exchange calendar for CME for Agriculture products 43 | 44 | Products: 45 | - Grains and Oilseeds (same trading hours and holidays) 46 | - Livestock 47 | - Dairy 48 | - Fertilizer 49 | - Lumber and Softs 50 | 51 | 52 | """ 53 | 54 | @property 55 | @abstractmethod 56 | def name(self): 57 | """ 58 | Name of the market 59 | 60 | :return: string name 61 | """ 62 | raise NotImplementedError() 63 | 64 | 65 | class CMEGlobexLivestockExchangeCalendar(CMEGlobexAgricultureExchangeCalendar): 66 | """ 67 | Exchange calendar for CME for Livestock products 68 | 69 | https://www.cmegroup.com/trading/agricultural/livestock.html 70 | 71 | GLOBEX Trading Times 72 | https://www.cmegroup.com/markets/agriculture/livestock/live-cattle.contractSpecs.html 73 | Monday - Friday: 8:30 a.m. - 1:05 p.m. CT 74 | """ 75 | 76 | aliases = [ 77 | "CMEGlobex_Livestock", 78 | "CMEGlobex_Live_Cattle", 79 | "CMEGlobex_Feeder_Cattle", 80 | "CMEGlobex_Lean_Hog", 81 | "CMEGlobex_Port_Cutout", 82 | ] 83 | 84 | regular_market_times = { 85 | "market_open": ((None, time(8, 30)),), 86 | "market_close": ((None, time(13, 5)),), 87 | } 88 | 89 | @property 90 | def name(self): 91 | return "CMEGlobex_Livestock" 92 | 93 | @property 94 | def regular_holidays(self): 95 | return AbstractHolidayCalendar( 96 | rules=[ 97 | USNewYearsDay, 98 | USMartinLutherKingJrAfter1998, 99 | USPresidentsDay, 100 | GoodFriday, 101 | USMemorialDay, 102 | USIndependenceDay, 103 | USLaborDay, 104 | USThanksgivingDay, 105 | Christmas, 106 | ] 107 | ) 108 | 109 | # @property 110 | # def adhoc_holidays(self): 111 | # return USNationalDaysofMourning 112 | 113 | @property 114 | def special_closes(self): 115 | return [ 116 | ( 117 | time(12, 5), 118 | AbstractHolidayCalendar( 119 | rules=[ 120 | USBlackFridayInOrAfter1993, 121 | ChristmasEveBefore1993, 122 | ChristmasEveInOrAfter1993, 123 | ] 124 | ), 125 | ) 126 | ] 127 | 128 | 129 | class CMEGlobexGrainsAndOilseedsExchangeCalendar(CMEGlobexAgricultureExchangeCalendar): 130 | """ 131 | Exchange calendar for CME for Grains & Oilseeds 132 | 133 | https://www.cmegroup.com/trading/agricultural/grain-and-oilseed.html 134 | 135 | GLOBEX Trading Times 136 | https://www.cmegroup.com/markets/agriculture/oilseeds/soybean.contractSpecs.html 137 | https://www.cmegroup.com/markets/agriculture/grains/corn.contractSpecs.html 138 | https://www.cmegroup.com/markets/agriculture/grains/wheat.contractSpecs.html 139 | Sunday - Friday: 7:00 p.m. - 7:45 a.m. CT and Monday - Friday: 8:30 a.m. - 1:20 p.m. CT 140 | """ 141 | 142 | aliases = [ 143 | "CMEGlobex_Grains", 144 | "CMEGlobex_Oilseeds", 145 | ] 146 | 147 | regular_market_times = { 148 | "market_open": ((None, time(19), -1),), # offset by -1 day 149 | "market_close": ((None, time(13, 20)),), 150 | "break_start": ((None, time(7, 45)),), 151 | "break_end": ((None, time(8, 30)),), 152 | } 153 | 154 | @property 155 | def name(self): 156 | return "CMEGlobex_GrainsAndOilseeds" 157 | 158 | @property 159 | def regular_holidays(self): 160 | return AbstractHolidayCalendar( 161 | rules=[ 162 | USNewYearsDay, 163 | USMartinLutherKingJrAfter1998, 164 | USPresidentsDay, 165 | GoodFriday, 166 | USMemorialDay, 167 | USIndependenceDay, 168 | USLaborDay, 169 | USThanksgivingDay, 170 | Christmas, 171 | ] 172 | ) 173 | -------------------------------------------------------------------------------- /pandas_market_calendars/holidays/uk.py: -------------------------------------------------------------------------------- 1 | # UK Holidays 2 | 3 | import pandas as pd 4 | from pandas import DateOffset, Timestamp 5 | from pandas.tseries.holiday import Holiday, MO, previous_friday, weekend_to_monday 6 | 7 | from pandas_market_calendars.market_calendar import MONDAY, TUESDAY 8 | 9 | # New Year's Eve 10 | LSENewYearsEve = Holiday( 11 | "New Year's Eve", 12 | month=12, 13 | day=31, 14 | observance=previous_friday, 15 | ) 16 | 17 | # New Year's Day 18 | LSENewYearsDay = Holiday( 19 | "New Year's Day", 20 | month=1, 21 | day=1, 22 | observance=weekend_to_monday, 23 | ) 24 | 25 | # Early May bank holiday has two exceptions based on the 50th and 75th anniversary of VE-Day 26 | # 1995-05-01 Early May bank holiday removed for VE-day 50th anniversary 27 | # 2020-05-04 Early May bank holiday removed for VE-day 75th anniversary 28 | 29 | # Early May bank holiday pre-1995 30 | MayBank_pre_1995 = Holiday( 31 | "Early May Bank Holiday", 32 | month=5, 33 | offset=DateOffset(weekday=MO(1)), 34 | day=1, 35 | end_date=Timestamp("1994-12-31"), 36 | ) 37 | 38 | # Early May bank holiday post-1995 and pre-2020 39 | MayBank_post_1995_pre_2020 = Holiday( 40 | "Early May Bank Holiday", 41 | month=5, 42 | offset=DateOffset(weekday=MO(1)), 43 | day=1, 44 | start_date=Timestamp("1996-01-01"), 45 | end_date=Timestamp("2019-12-31"), 46 | ) 47 | 48 | # Early May bank holiday post 2020 49 | MayBank_post_2020 = Holiday( 50 | "Early May Bank Holiday", 51 | month=5, 52 | offset=DateOffset(weekday=MO(1)), 53 | day=1, 54 | start_date=Timestamp("2021-01-01"), 55 | ) 56 | 57 | # Spring bank holiday has two exceptions based on the Golden & Diamond Jubilee 58 | # 2002-05-27 Spring bank holiday removed for Golden Jubilee 59 | # 2012-05-28 Spring bank holiday removed for Diamond Jubilee 60 | # 2022-05-31 Spring bank holiday removed for Platinum Jubilee 61 | 62 | # Spring bank holiday 63 | SpringBank_pre_2002 = Holiday( 64 | "Spring Bank Holiday", 65 | month=5, 66 | day=31, 67 | offset=DateOffset(weekday=MO(-1)), 68 | end_date=Timestamp("2001-12-31"), 69 | ) 70 | 71 | SpringBank_post_2002_pre_2012 = Holiday( 72 | "Spring Bank Holiday", 73 | month=5, 74 | day=31, 75 | offset=DateOffset(weekday=MO(-1)), 76 | start_date=Timestamp("2003-01-01"), 77 | end_date=Timestamp("2011-12-31"), 78 | ) 79 | 80 | SpringBank_post_2012_pre_2022 = Holiday( 81 | "Spring Bank Holiday", 82 | month=5, 83 | day=31, 84 | offset=DateOffset(weekday=MO(-1)), 85 | start_date=Timestamp("2013-01-01"), 86 | end_date=Timestamp("2021-12-31"), 87 | ) 88 | 89 | SpringBank_post_2022 = Holiday( 90 | "Spring Bank Holiday", 91 | month=5, 92 | day=31, 93 | offset=DateOffset(weekday=MO(-1)), 94 | start_date=Timestamp("2022-01-01"), 95 | ) 96 | 97 | # Summer bank holiday 98 | SummerBank = Holiday( 99 | "Summer Bank Holiday", 100 | month=8, 101 | day=31, 102 | offset=DateOffset(weekday=MO(-1)), 103 | ) 104 | 105 | # Christmas Eve 106 | ChristmasEve = Holiday( 107 | "Christmas Eve", 108 | month=12, 109 | day=24, 110 | observance=previous_friday, 111 | ) 112 | 113 | # Christmas 114 | Christmas = Holiday( 115 | "Christmas", 116 | month=12, 117 | day=25, 118 | ) 119 | 120 | # If christmas day is Saturday Monday 27th is a holiday 121 | # If christmas day is sunday the Tuesday 27th is a holiday 122 | WeekendChristmas = Holiday( 123 | "Weekend Christmas", 124 | month=12, 125 | day=27, 126 | days_of_week=(MONDAY, TUESDAY), 127 | ) 128 | 129 | # Boxing day 130 | BoxingDay = Holiday( 131 | "Boxing Day", 132 | month=12, 133 | day=26, 134 | ) 135 | 136 | # If boxing day is saturday then Monday 28th is a holiday 137 | # If boxing day is sunday then Tuesday 28th is a holiday 138 | WeekendBoxingDay = Holiday( 139 | "Weekend Boxing Day", 140 | month=12, 141 | day=28, 142 | days_of_week=(MONDAY, TUESDAY), 143 | ) 144 | 145 | # One-off holiday additions and removals in England 146 | 147 | UniqueCloses = [] 148 | # VE-Day Anniversary 149 | UniqueCloses.append(pd.Timestamp("1995-05-08", tz="UTC")) # 50th Anniversary 150 | UniqueCloses.append(pd.Timestamp("2020-05-08", tz="UTC")) # 75th Anniversary 151 | 152 | # Queen Elizabeth II Jubilees 153 | # Silver Jubilee 154 | UniqueCloses.append(pd.Timestamp("1977-06-07", tz="UTC")) 155 | 156 | # Golden Jubilee 157 | UniqueCloses.append(pd.Timestamp("2002-06-03", tz="UTC")) 158 | UniqueCloses.append(pd.Timestamp("2002-06-04", tz="UTC")) 159 | 160 | # Diamond Jubilee 161 | UniqueCloses.append(pd.Timestamp("2012-06-04", tz="UTC")) 162 | UniqueCloses.append(pd.Timestamp("2012-06-05", tz="UTC")) 163 | 164 | # Platinum Jubilee 165 | UniqueCloses.append(pd.Timestamp("2022-06-02", tz="UTC")) 166 | UniqueCloses.append(pd.Timestamp("2022-06-03", tz="UTC")) 167 | 168 | # State Funeral of Queen Elizabeth II 169 | UniqueCloses.append(pd.Timestamp("2022-09-19", tz="UTC")) 170 | 171 | # Royal Weddings 172 | UniqueCloses.append(pd.Timestamp("1973-11-14", tz="UTC")) # Wedding Day of Princess Anne and Mark Phillips 173 | UniqueCloses.append(pd.Timestamp("1981-07-29", tz="UTC")) # Wedding Day of Prince Charles and Diana Spencer 174 | UniqueCloses.append(pd.Timestamp("2011-04-29", tz="UTC")) # Wedding Day of Prince William and Catherine Middleton 175 | 176 | # Coronation of King Charles III 177 | UniqueCloses.append(pd.Timestamp("2023-05-08", tz="UTC")) 178 | 179 | # Miscellaneous 180 | UniqueCloses.append(pd.Timestamp("1999-12-31", tz="UTC")) # Eve of 3rd Millenium A.D. 181 | -------------------------------------------------------------------------------- /tests/test_ose_calendar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Currently only 2017, 2018 and 2019 dates are confirmed. 3 | 4 | Dates based on: 5 | - (not this is for OBX and Equity Derivatives) 6 | https://www.oslobors.no/obnewsletter/download/1e05ee05a9c1a472da4c715435ff1314/file/file/Bortfallskalender%202017-2019.pdf 7 | - https://www.oslobors.no/ob_eng/Oslo-Boers/About-Oslo-Boers/Opening-hours 8 | """ 9 | 10 | from zoneinfo import ZoneInfo 11 | 12 | import pandas as pd 13 | import pytest 14 | 15 | from pandas_market_calendars.calendars.ose import OSEExchangeCalendar 16 | 17 | TIMEZONE = ZoneInfo("Europe/Oslo") 18 | 19 | 20 | def test_time_zone(): 21 | assert OSEExchangeCalendar().tz == ZoneInfo("Europe/Oslo") 22 | 23 | 24 | def test_name(): 25 | assert OSEExchangeCalendar().name == "OSE" 26 | 27 | 28 | def test_open_time_tz(): 29 | ose = OSEExchangeCalendar() 30 | assert ose.open_time.tzinfo == ose.tz 31 | 32 | 33 | def test_close_time_tz(): 34 | ose = OSEExchangeCalendar() 35 | assert ose.close_time.tzinfo == ose.tz 36 | 37 | 38 | def test_2017_calendar(): 39 | ose = OSEExchangeCalendar() 40 | ose_schedule = ose.schedule( 41 | start_date=pd.Timestamp("2017-04-11", tz=TIMEZONE), 42 | end_date=pd.Timestamp("2017-04-13", tz=TIMEZONE), 43 | ) 44 | 45 | regular_holidays_2017 = [ 46 | pd.Timestamp("2017-04-13", tz=TIMEZONE), 47 | pd.Timestamp("2017-04-14", tz=TIMEZONE), 48 | pd.Timestamp("2017-04-17", tz=TIMEZONE), 49 | pd.Timestamp("2017-05-01", tz=TIMEZONE), 50 | pd.Timestamp("2017-05-17", tz=TIMEZONE), 51 | pd.Timestamp("2017-05-25", tz=TIMEZONE), 52 | pd.Timestamp("2017-06-05", tz=TIMEZONE), 53 | pd.Timestamp("2017-12-25", tz=TIMEZONE), 54 | pd.Timestamp("2017-12-26", tz=TIMEZONE), 55 | ] 56 | 57 | half_trading_days_2017 = [pd.Timestamp("2017-04-12", tz=TIMEZONE)] 58 | 59 | valid_market_dates = ose.valid_days("2017-01-01", "2017-12-31", tz=TIMEZONE) 60 | 61 | for closed_market_date in regular_holidays_2017: 62 | assert closed_market_date not in valid_market_dates 63 | 64 | for half_trading_day in half_trading_days_2017: 65 | assert half_trading_day in valid_market_dates 66 | 67 | assert ose.open_at_time(schedule=ose_schedule, timestamp=pd.Timestamp("2017-04-12 12PM", tz=TIMEZONE)) 68 | with pytest.raises(ValueError): 69 | ose.open_at_time(schedule=ose_schedule, timestamp=pd.Timestamp("2017-04-12 2PM", tz=TIMEZONE)) 70 | 71 | 72 | def test_2018_calendar(): 73 | ose = OSEExchangeCalendar() 74 | ose_schedule = ose.schedule( 75 | start_date=pd.Timestamp("2018-03-27", tz=TIMEZONE), 76 | end_date=pd.Timestamp("2018-03-29", tz=TIMEZONE), 77 | ) 78 | 79 | regular_holidays_2018 = [ 80 | pd.Timestamp("2018-01-01", tz=TIMEZONE), 81 | pd.Timestamp("2018-03-29", tz=TIMEZONE), 82 | pd.Timestamp("2018-03-30", tz=TIMEZONE), 83 | pd.Timestamp("2018-05-01", tz=TIMEZONE), 84 | pd.Timestamp("2018-05-10", tz=TIMEZONE), 85 | pd.Timestamp("2018-05-17", tz=TIMEZONE), 86 | pd.Timestamp("2018-05-21", tz=TIMEZONE), 87 | pd.Timestamp("2018-12-24", tz=TIMEZONE), 88 | pd.Timestamp("2018-12-25", tz=TIMEZONE), 89 | pd.Timestamp("2018-12-26", tz=TIMEZONE), 90 | pd.Timestamp("2018-12-31", tz=TIMEZONE), 91 | ] 92 | 93 | half_trading_days_2018 = [pd.Timestamp("2018-03-28", tz=TIMEZONE)] 94 | 95 | valid_market_dates = ose.valid_days("2018-01-01", "2018-12-31", tz=TIMEZONE) 96 | 97 | for closed_market_date in regular_holidays_2018: 98 | assert closed_market_date not in valid_market_dates 99 | 100 | for half_trading_day in half_trading_days_2018: 101 | assert half_trading_day in valid_market_dates 102 | 103 | assert ose.open_at_time(schedule=ose_schedule, timestamp=pd.Timestamp("2018-03-28 12PM", tz=TIMEZONE)) 104 | with pytest.raises(ValueError): 105 | ose.open_at_time( 106 | schedule=ose_schedule, 107 | timestamp=pd.Timestamp("2018-03-28 1:10PM", tz=TIMEZONE), 108 | ) 109 | 110 | 111 | def test_2019_calendar(): 112 | ose = OSEExchangeCalendar() 113 | ose_schedule = ose.schedule( 114 | start_date=pd.Timestamp("2019-04-16", tz=TIMEZONE), 115 | end_date=pd.Timestamp("2019-04-18", tz=TIMEZONE), 116 | ) 117 | 118 | regular_holidays_2019 = [ 119 | pd.Timestamp("2019-01-01", tz=TIMEZONE), 120 | pd.Timestamp("2019-04-18", tz=TIMEZONE), 121 | pd.Timestamp("2019-04-19", tz=TIMEZONE), 122 | pd.Timestamp("2019-04-22", tz=TIMEZONE), 123 | pd.Timestamp("2019-05-01", tz=TIMEZONE), 124 | pd.Timestamp("2019-05-17", tz=TIMEZONE), 125 | pd.Timestamp("2019-05-30", tz=TIMEZONE), 126 | pd.Timestamp("2019-06-10", tz=TIMEZONE), 127 | pd.Timestamp("2019-12-24", tz=TIMEZONE), 128 | pd.Timestamp("2019-12-25", tz=TIMEZONE), 129 | pd.Timestamp("2019-12-26", tz=TIMEZONE), 130 | pd.Timestamp("2019-12-31", tz=TIMEZONE), 131 | ] 132 | 133 | half_trading_days_2019 = [pd.Timestamp("2019-04-17", tz=TIMEZONE)] 134 | 135 | valid_market_dates = ose.valid_days("2019-01-01", "2019-12-31", tz=TIMEZONE) 136 | 137 | for closed_market_date in regular_holidays_2019: 138 | assert closed_market_date not in valid_market_dates 139 | 140 | for half_trading_day in half_trading_days_2019: 141 | assert half_trading_day in valid_market_dates 142 | 143 | assert ose.open_at_time(schedule=ose_schedule, timestamp=pd.Timestamp("2019-04-17 12PM", tz=TIMEZONE)) 144 | with pytest.raises(ValueError): 145 | ose.open_at_time( 146 | schedule=ose_schedule, 147 | timestamp=pd.Timestamp("2019-04-17 1:10PM", tz=TIMEZONE), 148 | ) 149 | -------------------------------------------------------------------------------- /tests/test_lse_calendar.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | from zoneinfo import ZoneInfo 3 | 4 | import pandas as pd 5 | 6 | from pandas_market_calendars.calendars.lse import LSEExchangeCalendar 7 | 8 | 9 | def test_time_zone(): 10 | assert LSEExchangeCalendar().tz == ZoneInfo("Europe/London") 11 | 12 | 13 | def test_2012_holidays(): 14 | # 2012/06/04 - Bank Holiday 15 | # 2012/06/05 - Queen's Diamond Jubilee 16 | lse = LSEExchangeCalendar() 17 | good_dates = lse.valid_days("2012-01-01", "2012-12-31") 18 | for date in ["2012-06-04", "2012-06-05"]: 19 | assert pd.Timestamp(date, tz="UTC") not in good_dates 20 | 21 | 22 | def test_2016_holidays(): 23 | # 2016/01/01 - New Years Day (observed on first business day on/after) 24 | # 2016/03/25 - Good Friday 25 | # 2016/03/28 - Easter Monday 26 | # 2016/05/02 - Early May Bank Holiday (first Monday in May) 27 | # 2016/05/30 - Spring Bank Holiday (last Monday in May) 28 | # 2016/08/29 - Summer Bank Holiday (last Monday in August) 29 | # 2016/12/27 - Dec. 27th (Christmas is on a weekend) 30 | # 2016/12/26 - Boxing Day 31 | lse = LSEExchangeCalendar() 32 | good_dates = lse.valid_days("2016-01-01", "2016-12-31") 33 | for date in [ 34 | "2016-01-01", 35 | "2016-03-25", 36 | "2016-03-28", 37 | "2016-05-02", 38 | "2016-05-30", 39 | "2016-08-29", 40 | "2016-12-27", 41 | "2016-12-26", 42 | ]: 43 | assert pd.Timestamp(date, tz="UTC") not in good_dates 44 | 45 | 46 | def test_2016_early_closes(): 47 | # Christmas Eve: 2016-12-23 48 | # New Year's Eve: 2016-12-30 49 | 50 | lse = LSEExchangeCalendar() 51 | schedule = lse.schedule("2016-01-01", "2017-12-31") 52 | early_closes = lse.early_closes(schedule).index 53 | 54 | for date in ["2016-12-23", "2016-12-30", "2017-12-22", "2017-12-29"]: 55 | dt = pd.Timestamp(date) 56 | assert dt in early_closes 57 | 58 | market_close = schedule.loc[dt].market_close 59 | assert market_close.tz_convert(lse.tz).hour == 12 60 | assert market_close.tz_convert(lse.tz).minute == 30 61 | 62 | 63 | def test_unique_holidays(): 64 | england_unique_hols_names = [ 65 | "VE_50", 66 | "VE_75", 67 | "QEII_Jubilee_25", 68 | "QEII_Jubilee_50", 69 | "QEII_Jubilee_60", 70 | "QEII_StateFuneral", 71 | "Royal_Wedding_Anne_1973", 72 | "Royal_Wedding_Charles_1981", 73 | "Royal_Wedding_William_2011", 74 | "KCIII_Coronation", 75 | "3rd_Millennium_Eve", 76 | ] 77 | england_unique_hols = {i: {"closed": None, "open": None} for i in england_unique_hols_names} 78 | 79 | # One-off holiday additions and removals in England 80 | 81 | # VE-Day Anniversary 82 | # 50th Anniversary 83 | england_unique_hols["VE_50"]["closed"] = [pd.Timestamp("1995-05-08")] 84 | england_unique_hols["VE_50"]["open"] = [pd.Timestamp("1995-05-01")] # Early May bank holiday removed 85 | # 75th Anniversary 86 | england_unique_hols["VE_75"]["closed"] = [pd.Timestamp("2020-05-08")] 87 | england_unique_hols["VE_75"]["open"] = [pd.Timestamp("2020-05-04")] # Early May bank holiday removed 88 | 89 | # Queen Elizabeth II Jubilees 90 | # Silver Jubilee 91 | england_unique_hols["QEII_Jubilee_25"]["closed"] = [pd.Timestamp("1977-06-07")] 92 | # Golden Jubilee 93 | england_unique_hols["QEII_Jubilee_50"]["closed"] = [ 94 | pd.Timestamp("2002-06-03"), 95 | pd.Timestamp("2002-06-04"), 96 | ] 97 | england_unique_hols["QEII_Jubilee_50"]["open"] = [pd.Timestamp("2002-05-27")] # Spring bank holiday removed 98 | # Diamond Jubilee 99 | england_unique_hols["QEII_Jubilee_60"]["closed"] = [ 100 | pd.Timestamp("2012-06-04"), 101 | pd.Timestamp("2012-06-05"), 102 | ] 103 | england_unique_hols["QEII_Jubilee_60"]["open"] = [pd.Timestamp("2012-05-28")] # Spring bank holiday removed 104 | # Platinum Jubilee 105 | england_unique_hols["QEII_Jubilee_60"]["closed"] = [ 106 | pd.Timestamp("2022-06-02"), 107 | pd.Timestamp("2022-06-03"), 108 | ] 109 | england_unique_hols["QEII_Jubilee_60"]["open"] = [pd.Timestamp("2022-05-31")] # Spring bank holiday removed 110 | 111 | # State Funeral of Queen Elizabeth II 112 | england_unique_hols["QEII_StateFuneral"]["closed"] = [pd.Timestamp("2022-09-19")] 113 | 114 | # Royal Weddings 115 | # Wedding Day of Princess Anne and Mark Phillips 116 | england_unique_hols["Royal_Wedding_Anne_1973"]["closed"] = [pd.Timestamp("1973-11-14")] 117 | # Wedding Day of Prince Charles and Diana Spencer 118 | england_unique_hols["Royal_Wedding_Charles_1981"]["closed"] = [pd.Timestamp("1981-07-29")] 119 | # Wedding Day of Prince William and Catherine Middleton 120 | england_unique_hols["Royal_Wedding_William_2011"]["closed"] = [pd.Timestamp("2011-04-29")] 121 | 122 | # Coronation of King Charles III 123 | england_unique_hols["KCIII_Coronation"]["closed"] = [pd.Timestamp("2023-05-08")] 124 | 125 | # Miscellaneous 126 | # Eve of 3rd Millennium A.D. 127 | england_unique_hols["3rd_Millennium_Eve"]["closed"] = [pd.Timestamp("1999-12-31")] 128 | 129 | # Test of closed dates 130 | lse = LSEExchangeCalendar() 131 | # get all the closed dates 132 | closed_days = [england_unique_hols[k].get("closed") for k in england_unique_hols] 133 | good_dates = lse.valid_days("1990-01-01", "2022-12-31") 134 | for date in chain.from_iterable(closed_days): 135 | assert pd.Timestamp(date, tz="UTC") not in good_dates 136 | 137 | # Test of open dates 138 | open_days = [england_unique_hols[k].get("open") for k in england_unique_hols] 139 | open_days = [i for i in open_days if i] 140 | good_dates = lse.valid_days("1990-01-01", "2022-12-31") 141 | for date in chain.from_iterable(open_days): 142 | assert pd.Timestamp(date, tz="UTC") in good_dates 143 | -------------------------------------------------------------------------------- /tests/test_class_registry.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | from datetime import time 3 | from pprint import pformat 4 | 5 | import pytest 6 | 7 | from pandas_market_calendars.class_registry import RegisteryMeta, ProtectedDict 8 | 9 | 10 | def test_inheritance(): 11 | class Base(object): 12 | regular_market_times = { 13 | "market_open": {None: time(0)}, 14 | "market_close": {None: time(23)}, 15 | } 16 | open_close_map = {} 17 | 18 | @classmethod 19 | def _prepare_regular_market_times(cls): 20 | return 21 | 22 | def __init__(self, arg0, kw0=None): 23 | self.arg0 = arg0 24 | self.kw0 = kw0 25 | super(Base, self).__init__() 26 | 27 | def special_opens(self): 28 | return [] 29 | 30 | special_closes = special_closes_adhoc = special_opens_adhoc = special_opens 31 | 32 | class Class1(Base, metaclass=RegisteryMeta): 33 | def __init__(self, arg0, arg1, kw1=None, **kwargs): 34 | self.arg1 = arg1 35 | self.kw1 = kw1 36 | super(Class1, self).__init__(arg0, **kwargs) 37 | 38 | factory1 = Class1.factory 39 | 40 | class Class2(Base, metaclass=RegisteryMeta): 41 | aliases = ["class 2"] 42 | 43 | def __init__(self, arg0, arg2, kw2=None, **kwargs): 44 | self.arg2 = arg2 45 | self.kw2 = kw2 46 | super(Class2, self).__init__(arg0, **kwargs) 47 | 48 | factory2 = Class2.factory 49 | 50 | class Class1a(Class1): 51 | aliases = ["class 1a"] 52 | 53 | def __init__(self, arg0, arg1, arg1a, kw1a=None, **kwargs): 54 | self.arg1a = arg1a 55 | self.kw1a = kw1a 56 | super(Class1a, self).__init__(arg0, arg1, **kwargs) 57 | 58 | class Class1b(Class1): 59 | def __init__(self, arg0, arg1, arg1b, kw1b=None, **kwargs): 60 | self.arg1b = arg1b 61 | self.kw1b = kw1b 62 | super(Class1b, self).__init__(arg0, arg1, **kwargs) 63 | 64 | class Class12a(Class1, Class2): 65 | aliases = ["class 12a"] 66 | 67 | def __init__(self, arg0, arg1, arg2, arg12a, kw12a=None, **kwargs): 68 | self.arg12a = arg12a 69 | self.kw12a = kw12a 70 | super(Class12a, self).__init__(arg0=arg0, arg1=arg1, arg2=arg2, **kwargs) 71 | 72 | assert set(Class1._regmeta_class_registry.keys()) == { 73 | "Class1", 74 | "class 1a", 75 | "Class1b", 76 | "class 12a", 77 | } 78 | assert set(Class2._regmeta_class_registry.keys()) == {"class 2", "class 12a"} 79 | 80 | o = factory1("Class1", "0", "1", kw0="k0", kw1="k1") 81 | assert (o.arg0, o.arg1, o.kw0, o.kw1) == ("0", "1", "k0", "k1") 82 | assert Class1 == o.__class__ 83 | 84 | o = factory1("class 1a", "0", "1", "a", kw0="k0", kw1="k1", kw1a="k1a") 85 | assert (o.arg0, o.arg1, o.arg1a, o.kw0, o.kw1, o.kw1a) == ( 86 | "0", 87 | "1", 88 | "a", 89 | "k0", 90 | "k1", 91 | "k1a", 92 | ) 93 | assert Class1a == o.__class__ 94 | 95 | o = factory1("Class1b", "0", "1", "b", kw0="k0", kw1="k1", kw1b="k1b") 96 | assert (o.arg0, o.arg1, o.arg1b, o.kw0, o.kw1, o.kw1b) == ( 97 | "0", 98 | "1", 99 | "b", 100 | "k0", 101 | "k1", 102 | "k1b", 103 | ) 104 | assert Class1b == o.__class__ 105 | 106 | o = factory1("class 12a", "0", "1", "2", "a", kw0="k0", kw1="k1", kw2="k2", kw12a="k12a") 107 | assert (o.arg0, o.arg1, o.arg2, o.arg12a, o.kw0, o.kw1, o.kw2, o.kw12a) == ( 108 | "0", 109 | "1", 110 | "2", 111 | "a", 112 | "k0", 113 | "k1", 114 | "k2", 115 | "k12a", 116 | ) 117 | assert Class12a == o.__class__ 118 | 119 | o = factory2("class 2", "0", "2", kw0="k0", kw2="k2") 120 | assert (o.arg0, o.arg2, o.kw0, o.kw2) == ("0", "2", "k0", "k2") 121 | assert Class2 == o.__class__ 122 | 123 | 124 | def test_metamixing(): 125 | BaseMeta = type("BaseMeta", (ABCMeta, RegisteryMeta), {}) 126 | 127 | class Base(metaclass=BaseMeta): 128 | regular_market_times = { 129 | "market_open": {None: time(0)}, 130 | "market_close": {None: time(23)}, 131 | } 132 | open_close_map = {} 133 | 134 | @classmethod 135 | def _prepare_regular_market_times(cls): 136 | return 137 | 138 | def special_opens(self): 139 | return [] 140 | 141 | special_closes = special_closes_adhoc = special_opens_adhoc = special_opens 142 | 143 | @abstractmethod 144 | def test(self): 145 | pass 146 | 147 | class Class1(Base): 148 | aliases = ["c1", "c 1"] 149 | 150 | def test(self): 151 | return 123 152 | 153 | try: 154 | Base() 155 | except TypeError: 156 | pass 157 | else: 158 | raise RuntimeError("Abstract class is instantiated") 159 | 160 | o1 = Base.factory("c1") 161 | o2 = Base.factory("c 1") 162 | assert o1.test() == 123 163 | assert o1.test() == o2.test() 164 | 165 | with pytest.raises(RuntimeError): 166 | Base.factory("error") # doesn't exist 167 | 168 | class Class2(Base): # no aliases 169 | def test(self): 170 | return "test" 171 | 172 | assert Base.factory("Class2").test() == "test" 173 | 174 | 175 | def test_protected_dict(): 176 | dct = ProtectedDict(dict(a=1, b=2)) 177 | 178 | with pytest.raises(TypeError): 179 | dct["a"] = 2 180 | 181 | with pytest.raises(TypeError): 182 | del dct["b"] 183 | 184 | del dct._INIT_RAN_NORMALLY 185 | del dct["b"] 186 | 187 | dct = ProtectedDict(dict(a=1, b=2)) 188 | 189 | s = "ProtectedDict(\n" + pformat(dict(dct), sort_dicts=False) + "\n)" 190 | assert str(dct) == s 191 | 192 | 193 | # if __name__ == '__main__': 194 | # 195 | # for ref, obj in locals().copy().items(): 196 | # if ref.startswith("test_"): 197 | # print("running: ", ref) 198 | # obj() 199 | -------------------------------------------------------------------------------- /pandas_market_calendars/holidays/cme_globex.py: -------------------------------------------------------------------------------- 1 | from dateutil.relativedelta import MO, TH 2 | from pandas import DateOffset, Timestamp 3 | from pandas.tseries.holiday import Holiday, nearest_workday, Easter 4 | from pandas.tseries.offsets import Day 5 | 6 | from pandas_market_calendars.market_calendar import ( 7 | MONDAY, 8 | TUESDAY, 9 | WEDNESDAY, 10 | THURSDAY, 11 | FRIDAY, 12 | ) 13 | 14 | #################################################### 15 | # US New Years Day Jan 1 16 | ##################################################### 17 | USNewYearsDay = Holiday( 18 | "New Years Day", 19 | month=1, 20 | day=1, 21 | start_date=Timestamp("1952-09-29"), 22 | # observance=sunday_to_monday, 23 | days_of_week=( 24 | MONDAY, 25 | TUESDAY, 26 | WEDNESDAY, 27 | THURSDAY, 28 | FRIDAY, 29 | ), 30 | ) 31 | 32 | ######################################################################### 33 | # Martin Luther King Jr. 34 | # Starting 1998 35 | ########################################################################## 36 | USMartinLutherKingJrFrom2022 = Holiday( 37 | "Dr. Martin Luther King Jr. Day", 38 | month=1, 39 | day=1, 40 | start_date=Timestamp("2022-01-01"), 41 | days_of_week=( 42 | MONDAY, 43 | TUESDAY, 44 | WEDNESDAY, 45 | THURSDAY, 46 | FRIDAY, 47 | ), 48 | offset=DateOffset(weekday=MO(3)), 49 | ) 50 | 51 | USMartinLutherKingJrPre2022 = Holiday( 52 | "Dr. Martin Luther King Jr. Day", 53 | month=1, 54 | day=1, 55 | start_date=Timestamp("1998-01-01"), 56 | end_date=Timestamp("2021-12-31"), 57 | offset=DateOffset(weekday=MO(3)), 58 | ) 59 | 60 | ######################################################################### 61 | # US Presidents Day Feb 62 | ########################################################################## 63 | USPresidentsDayFrom2022 = Holiday( 64 | "President" "s Day", 65 | start_date=Timestamp("2022-01-01"), 66 | month=2, 67 | day=1, 68 | offset=DateOffset(weekday=MO(3)), 69 | ) 70 | 71 | USPresidentsDayPre2022 = Holiday( 72 | "President" "s Day", 73 | end_date=Timestamp("2021-12-31"), 74 | month=2, 75 | day=1, 76 | offset=DateOffset(weekday=MO(3)), 77 | ) 78 | 79 | ############################################################ 80 | # Good Friday 81 | ############################################################ 82 | GoodFriday = Holiday( 83 | "Good Friday 1908+", 84 | start_date=Timestamp("1908-01-01"), 85 | month=1, 86 | day=1, 87 | offset=[Easter(), Day(-2)], 88 | ) 89 | 90 | ################################################## 91 | # US Memorial Day (Decoration Day) May 30 92 | ################################################## 93 | USMemorialDayFrom2022 = Holiday( 94 | "Memorial Day", 95 | month=5, 96 | day=25, 97 | start_date=Timestamp("2022-01-01"), 98 | offset=DateOffset(weekday=MO(1)), 99 | ) 100 | 101 | USMemorialDayPre2022 = Holiday( 102 | "Memorial Day", 103 | month=5, 104 | day=25, 105 | end_date=Timestamp("2021-12-31"), 106 | offset=DateOffset(weekday=MO(1)), 107 | ) 108 | 109 | ####################################### 110 | # US Juneteenth (June 19th) 111 | ####################################### 112 | USJuneteenthFrom2022 = Holiday( 113 | "Juneteenth Starting at 2022", 114 | start_date=Timestamp("2022-06-19"), 115 | month=6, 116 | day=19, 117 | observance=nearest_workday, 118 | ) 119 | 120 | ####################################### 121 | # US Independence Day July 4 122 | ####################################### 123 | USIndependenceDayFrom2022 = Holiday( 124 | "July 4th", 125 | month=7, 126 | day=4, 127 | start_date=Timestamp("2022-01-01"), 128 | observance=nearest_workday, 129 | ) 130 | USIndependenceDayPre2022 = Holiday( 131 | "July 4th", 132 | month=7, 133 | day=4, 134 | end_date=Timestamp("2021-12-31"), 135 | observance=nearest_workday, 136 | ) 137 | 138 | ################################################# 139 | # US Labor Day Starting 1887 140 | ################################################# 141 | USLaborDayFrom2022 = Holiday( 142 | "Labor Day", 143 | month=9, 144 | day=1, 145 | start_date=Timestamp("2022-01-01"), 146 | offset=DateOffset(weekday=MO(1)), 147 | ) 148 | USLaborDayPre2022 = Holiday( 149 | "Labor Day", 150 | month=9, 151 | day=1, 152 | end_date=Timestamp("2021-12-31"), 153 | offset=DateOffset(weekday=MO(1)), 154 | ) 155 | USLaborDay = Holiday( 156 | "Labor Day", 157 | month=9, 158 | day=1, 159 | start_date=Timestamp("1887-01-01"), 160 | offset=DateOffset(weekday=MO(1)), 161 | ) 162 | 163 | ################################################ 164 | # US Thanksgiving Nov 30 165 | ################################################ 166 | USThanksgivingDayFrom2022 = Holiday( 167 | "Thanksgiving", 168 | start_date=Timestamp("2022-01-01"), 169 | month=11, 170 | day=1, 171 | offset=DateOffset(weekday=TH(4)), 172 | ) 173 | 174 | USThanksgivingDayPre2022 = Holiday( 175 | "Thanksgiving", 176 | end_date=Timestamp("2021-12-31"), 177 | month=11, 178 | day=1, 179 | offset=DateOffset(weekday=TH(4)), 180 | ) 181 | 182 | FridayAfterThanksgiving = Holiday( 183 | "Friday after Thanksgiving", 184 | month=11, 185 | day=1, 186 | offset=[DateOffset(weekday=TH(4)), Day(1)], 187 | ) 188 | 189 | USThanksgivingFridayFrom2021 = Holiday( 190 | "Thanksgiving Friday", 191 | month=11, 192 | day=1, 193 | offset=[DateOffset(weekday=TH(4)), Day(1)], 194 | start_date=Timestamp("2021-01-01"), 195 | ) 196 | 197 | USThanksgivingFridayPre2021 = Holiday( 198 | "Thanksgiving Friday", 199 | month=11, 200 | day=1, 201 | offset=[DateOffset(weekday=TH(4)), Day(1)], 202 | end_date=Timestamp("2020-12-31"), 203 | ) 204 | 205 | ################################ 206 | # Christmas Dec 25 207 | ################################ 208 | ChristmasCME = Holiday( 209 | "Christmas", 210 | month=12, 211 | day=25, 212 | start_date=Timestamp("1999-01-01"), 213 | observance=nearest_workday, 214 | ) 215 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_crypto.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | 3 | import sys 4 | 5 | # check python versiOn aNd import accordingly 6 | if sys.version_info >= (3, 9): 7 | # For Python 3.9 and later, import directly 8 | from zoneinfo import ZoneInfo 9 | else: 10 | # For Python 3.8 and earlier, import from backports 11 | from backports.zoneinfo import ZoneInfo 12 | from pandas.tseries.holiday import AbstractHolidayCalendar 13 | 14 | from pandas_market_calendars.holidays.cme import ( 15 | GoodFriday2021, 16 | GoodFriday2022, 17 | GoodFridayAfter2022, 18 | GoodFridayBefore2021, 19 | USIndependenceDayBefore2022PreviousDay, 20 | ) 21 | from pandas_market_calendars.holidays.cme_globex import ( 22 | ChristmasCME, 23 | USMartinLutherKingJrFrom2022, 24 | USMartinLutherKingJrPre2022, 25 | USPresidentsDayFrom2022, 26 | USPresidentsDayPre2022, 27 | USMemorialDayFrom2022, 28 | USMemorialDayPre2022, 29 | USJuneteenthFrom2022, 30 | USIndependenceDayFrom2022, 31 | USIndependenceDayPre2022, 32 | USLaborDayFrom2022, 33 | USLaborDayPre2022, 34 | USThanksgivingDayFrom2022, 35 | USThanksgivingDayPre2022, 36 | USThanksgivingFridayFrom2021, 37 | USThanksgivingFridayPre2021, 38 | ) 39 | from pandas_market_calendars.holidays.us import ( 40 | ChristmasEveInOrAfter1993, 41 | USNewYearsDay, 42 | ) 43 | from .cme_globex_base import CMEGlobexBaseExchangeCalendar 44 | 45 | 46 | # https://github.com/rsheftel/pandas_market_calendars/blob/master/docs/new_market.rst 47 | class CMEGlobexCryptoExchangeCalendar(CMEGlobexBaseExchangeCalendar): 48 | # The label you fetch the exchange with in mcal.get_calendar('CME Globex ...') 49 | aliases = ["CME Globex Cryptocurrencies", "CME Globex Crypto"] 50 | 51 | # https://www.cmegroup.com/markets/cryptocurrencies/bitcoin/bitcoin.contractSpecs.html 52 | regular_market_times = { 53 | # Tuple[Tuple[first date used, time, offset], ...] 54 | # -1 offset indicates that the open is on the previous day 55 | # None for first date used marks the start, subsequent market times must have an actual timestamp 56 | "market_open": ((None, dt.time(17, tzinfo=ZoneInfo("America/Chicago")), -1),), 57 | "market_close": ( 58 | ( 59 | None, 60 | dt.time(16, tzinfo=ZoneInfo("America/Chicago")), 61 | ), 62 | ), 63 | "break_start": ( 64 | ( 65 | None, 66 | dt.time(16, tzinfo=ZoneInfo("America/Chicago")), 67 | ), 68 | ), 69 | "break_end": ( 70 | ( 71 | None, 72 | dt.time(17, tzinfo=ZoneInfo("America/Chicago")), 73 | ), 74 | ), 75 | } 76 | 77 | @property 78 | def tz(self): 79 | # Central Time 80 | return ZoneInfo("America/Chicago") 81 | 82 | @property 83 | def name(self): 84 | return "CME Globex Crypto" 85 | 86 | # Check the .zip files at the bottom of this page 87 | # https://www.cmegroup.com/tools-information/holiday-calendar.html?redirect=/tools-information/holiday-calendar/#cmeGlobex 88 | # Note: many of the holiday objects (ie. GoodFridayBefore2021) were originally made for equities and other markets 89 | # and hence have a start_date starting before crypto is actually available 90 | 91 | @property 92 | def regular_holidays(self): 93 | # Days where the market is fully closed 94 | return AbstractHolidayCalendar( 95 | rules=[ 96 | GoodFridayBefore2021, 97 | GoodFriday2022, 98 | ChristmasCME, 99 | USNewYearsDay, 100 | ] 101 | ) 102 | 103 | @property 104 | def special_closes(self): 105 | # Days where the market closes early 106 | # list[Tuple[time, AbstractHolidayCalendar]] 107 | return [ 108 | ( 109 | dt.time(8, 15, tzinfo=ZoneInfo("America/Chicago")), 110 | AbstractHolidayCalendar( 111 | rules=[ 112 | GoodFriday2021, 113 | ] 114 | ), 115 | ), 116 | ( 117 | dt.time(10, 15, tzinfo=ZoneInfo("America/Chicago")), 118 | AbstractHolidayCalendar( 119 | rules=[ 120 | GoodFridayAfter2022, 121 | ] 122 | ), 123 | ), 124 | ( 125 | dt.time(12, tzinfo=ZoneInfo("America/Chicago")), 126 | AbstractHolidayCalendar( 127 | rules=[ 128 | USMartinLutherKingJrPre2022, 129 | USPresidentsDayPre2022, 130 | USMemorialDayPre2022, 131 | USIndependenceDayPre2022, 132 | USLaborDayPre2022, 133 | USThanksgivingDayPre2022, 134 | ] 135 | ), 136 | ), 137 | ( 138 | dt.time(12, 15, tzinfo=ZoneInfo("America/Chicago")), 139 | AbstractHolidayCalendar( 140 | rules=[ 141 | ChristmasEveInOrAfter1993, 142 | USIndependenceDayBefore2022PreviousDay, 143 | USThanksgivingFridayPre2021, 144 | ] 145 | ), 146 | ), 147 | ( 148 | dt.time(12, 45, tzinfo=ZoneInfo("America/Chicago")), 149 | AbstractHolidayCalendar(rules=[USThanksgivingFridayFrom2021]), 150 | ), 151 | # TODO: this market already closes at 1600 normally, do we need these holidays? 152 | ( 153 | dt.time(16, tzinfo=ZoneInfo("America/Chicago")), 154 | AbstractHolidayCalendar( 155 | rules=[ 156 | USMartinLutherKingJrFrom2022, 157 | USPresidentsDayFrom2022, 158 | USMemorialDayFrom2022, 159 | USJuneteenthFrom2022, 160 | USIndependenceDayFrom2022, 161 | USLaborDayFrom2022, 162 | USThanksgivingDayFrom2022, 163 | ] 164 | ), 165 | ), 166 | ] 167 | -------------------------------------------------------------------------------- /tests/test_exchange_calendar_cme_globex_energy_and_metals.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | from pandas.testing import assert_index_equal 5 | 6 | from pandas_market_calendars.calendars.cme_globex_energy_and_metals import ( 7 | CMEGlobexEnergyAndMetalsExchangeCalendar, 8 | ) 9 | 10 | cal = CMEGlobexEnergyAndMetalsExchangeCalendar() 11 | 12 | 13 | def test_time_zone(): 14 | assert cal.tz == ZoneInfo("America/Chicago") 15 | assert cal.name == "CMEGlobex_EnergyAndMetals" 16 | 17 | 18 | def test_open_time_tz(): 19 | assert cal.open_time.tzinfo == cal.tz 20 | 21 | 22 | def test_close_time_tz(): 23 | assert cal.close_time.tzinfo == cal.tz 24 | 25 | 26 | def test_weekmask(): 27 | assert cal.weekmask == "Mon Tue Wed Thu Fri" 28 | 29 | 30 | def _test_holidays(holidays, start, end): 31 | df = pd.DataFrame(cal.holidays().holidays, columns=["holidays"]) 32 | mask = (df["holidays"] >= start) & (df["holidays"] <= end) 33 | df = df[mask] 34 | assert len(holidays) == len(df) 35 | df = df.set_index(["holidays"]) 36 | df.index = df.index.tz_localize("UTC") 37 | assert_index_equal(pd.DatetimeIndex(holidays), df.index, check_names=False) 38 | valid_days = cal.valid_days(start, end) 39 | for h in holidays: 40 | assert h not in valid_days 41 | 42 | 43 | def _test_no_special_opens(start, end): 44 | assert len(cal.late_opens(cal.schedule(start, end))) == 0 45 | 46 | 47 | def _test_no_special_closes(start, end): 48 | assert len(cal.early_closes(cal.schedule(start, end))) == 0 49 | 50 | 51 | def _test_no_special_opens_closes(start, end): 52 | _test_no_special_opens(start, end) 53 | _test_no_special_closes(start, end) 54 | 55 | 56 | def _test_verify_late_open_time(schedule, timestamp): 57 | date = pd.Timestamp(pd.Timestamp(timestamp).tz_convert("UTC").date()) 58 | if date in schedule.index: 59 | return schedule.at[date, "market_open"] == timestamp 60 | else: 61 | return False 62 | 63 | 64 | def _test_has_late_opens(late_opens, start, end): 65 | schedule = cal.schedule(start, end) 66 | expected = cal.late_opens(schedule) 67 | assert len(expected) == len(late_opens) 68 | for ts in late_opens: 69 | assert _test_verify_late_open_time(schedule, ts) is True 70 | 71 | 72 | def _test_verify_early_close_time(schedule, timestamp): 73 | date = pd.Timestamp(pd.Timestamp(timestamp).tz_convert("UTC").date()) 74 | if date in schedule.index: 75 | return schedule.at[date, "market_close"] == timestamp 76 | else: 77 | return False 78 | 79 | 80 | def _test_has_early_closes(early_closes, start, end): 81 | schedule = cal.schedule(start, end) 82 | expected = cal.early_closes(schedule) 83 | assert len(expected) == len(early_closes) 84 | for ts in early_closes: 85 | assert _test_verify_early_close_time(schedule, ts) is True 86 | 87 | 88 | ######################################################################### 89 | # YEARLY TESTS BEGIN 90 | ######################################################################### 91 | def test_2022(): 92 | start = "2022-01-01" 93 | end = "2022-12-31" 94 | holidays = [ 95 | pd.Timestamp("2022-04-15", tz="UTC"), # Good Friday 96 | pd.Timestamp("2022-12-26", tz="UTC"), # Christmas 97 | ] 98 | _test_holidays(holidays, start, end) 99 | _test_no_special_opens(start, end) 100 | 101 | early_closes = [ 102 | pd.Timestamp("2022-01-17 1:30PM", tz="America/Chicago"), # MLK 103 | pd.Timestamp("2022-02-21 1:30PM", tz="America/Chicago"), # Presidents Day 104 | pd.Timestamp("2022-05-30 1:30PM", tz="America/Chicago"), # Memorial Day 105 | pd.Timestamp("2022-06-20 1:30PM", tz="America/Chicago"), # Juneteenth 106 | pd.Timestamp("2022-07-04 1:30PM", tz="America/Chicago"), # Independence Day 107 | pd.Timestamp("2022-09-05 12:00PM", tz="America/Chicago"), # Labor Day 108 | pd.Timestamp("2022-11-24 1:30PM", tz="America/Chicago"), # US Thanksgiving 109 | pd.Timestamp("2022-11-25 12:45PM", tz="America/Chicago"), # Friday after US Thanksgiving 110 | ] 111 | _test_has_early_closes(early_closes, start, end) 112 | 113 | 114 | def test_2021(): 115 | start = "2021-01-01" 116 | end = "2021-12-31" 117 | holidays = [ 118 | pd.Timestamp("2021-01-01", tz="UTC"), # New Years 119 | pd.Timestamp("2021-04-02", tz="UTC"), # Good Friday 120 | pd.Timestamp("2021-12-24", tz="UTC"), # Christmas 121 | ] 122 | _test_holidays(holidays, start, end) 123 | _test_no_special_opens(start, end) 124 | 125 | early_closes = [ 126 | pd.Timestamp("2021-01-18 12:00PM", tz="America/Chicago"), # MLK 127 | pd.Timestamp("2021-02-15 12:00PM", tz="America/Chicago"), # Presidents Day 128 | pd.Timestamp("2021-05-31 12:00PM", tz="America/Chicago"), # Memorial Day 129 | pd.Timestamp("2021-07-05 12:00PM", tz="America/Chicago"), # Independence Day 130 | pd.Timestamp("2021-09-06 12:00PM", tz="America/Chicago"), # Labor Day 131 | pd.Timestamp("2021-11-25 12:00PM", tz="America/Chicago"), # US Thanksgiving 132 | pd.Timestamp("2021-11-26 12:45PM", tz="America/Chicago"), # Friday after US Thanksgiving 133 | ] 134 | _test_has_early_closes(early_closes, start, end) 135 | 136 | 137 | def test_2020(): 138 | start = "2020-01-01" 139 | end = "2020-12-31" 140 | holidays = [ 141 | pd.Timestamp("2020-01-01", tz="UTC"), # New Years 142 | pd.Timestamp("2020-04-10", tz="UTC"), # Good Friday 143 | pd.Timestamp("2020-12-25", tz="UTC"), # Christmas 144 | ] 145 | _test_holidays(holidays, start, end) 146 | _test_no_special_opens(start, end) 147 | 148 | early_closes = [ 149 | pd.Timestamp("2020-01-20 12:00PM", tz="America/Chicago"), # MLK 150 | pd.Timestamp("2020-02-17 12:00PM", tz="America/Chicago"), # Presidents Day 151 | pd.Timestamp("2020-05-25 12:00PM", tz="America/Chicago"), # Memorial Day 152 | pd.Timestamp("2020-07-03 12:00PM", tz="America/Chicago"), # Independence Day 153 | pd.Timestamp("2020-09-07 12:00PM", tz="America/Chicago"), # Labor Day 154 | pd.Timestamp("2020-11-26 12:00PM", tz="America/Chicago"), # US Thanksgiving 155 | pd.Timestamp("2020-11-27 12:45PM", tz="America/Chicago"), # Friday after US Thanksgiving 156 | ] 157 | _test_has_early_closes(early_closes, start, end) 158 | -------------------------------------------------------------------------------- /tests/test_exchange_calendar_cme_globex_fx.py: -------------------------------------------------------------------------------- 1 | from zoneinfo import ZoneInfo 2 | 3 | import pandas as pd 4 | import pytest 5 | from pandas.tseries.offsets import Day, Hour, Minute 6 | 7 | from pandas_market_calendars.calendars.cme_globex_fx import CMEGlobexFXExchangeCalendar 8 | 9 | TZ = "America/Chicago" 10 | 11 | 12 | def test_time_zone(): 13 | assert CMEGlobexFXExchangeCalendar().tz == ZoneInfo(TZ) 14 | assert CMEGlobexFXExchangeCalendar().name == "CMEGlobex_FX" 15 | 16 | 17 | def test_sunday_opens(): 18 | cme = CMEGlobexFXExchangeCalendar() 19 | schedule = cme.schedule("2020-01-01", "2020-01-31") 20 | assert pd.Timestamp("2020-01-12 17:00:00", tz=TZ) == schedule.loc["2020-01-13", "market_open"] 21 | 22 | 23 | @pytest.mark.parametrize( 24 | "day_status", 25 | [ 26 | # 2020 27 | # 2020 Martin Luther King Day (20th = Monday) 28 | ("2020-01-17", "open"), 29 | ("2020-01-20", "1200"), 30 | ("2020-01-21", "open"), 31 | # 2020 Presidents Day (17th = Monday) 32 | ("2020-02-14", "open"), 33 | ("2020-02-17", "1200"), 34 | ("2020-02-18", "open"), 35 | # 2020 Good Friday (10th = Friday) 36 | ("2020-04-09", "open"), 37 | ("2020-04-10", "closed"), 38 | ("2020-04-13", "open"), 39 | # 2020 Memorial Day (May 25 = Monday) 40 | ("2020-05-22", "open"), 41 | ("2020-05-25", "1200"), 42 | ("2020-05-26", "open"), 43 | # 2020 Independence Day (4th = Saturday) 44 | ("2020-07-02", "open"), 45 | ("2020-07-03", "1200"), 46 | ("2020-07-06", "open"), 47 | # 2020 Labor Day (4th = Monday) 48 | ("2020-09-04", "open"), 49 | ("2020-09-07", "1200"), 50 | ("2020-09-08", "open"), 51 | # 2020 Thanksgiving (26th = Thursday) 52 | ("2020-11-25", "open"), 53 | ("2020-11-26", "1200"), 54 | ("2020-11-27", "1215"), 55 | ("2020-11-30", "open"), 56 | # 2020 Christmas (25th = Friday) 57 | ("2020-12-24", "1215"), 58 | ("2020-12-25", "closed"), 59 | ("2020-12-28", "open"), 60 | ("2020-12-29", "open"), 61 | # 2020/21 New Year's (Dec 31 = Thur) 62 | ("2020-12-31", "open"), 63 | ("2021-01-01", "closed"), 64 | ("2022-01-04", "open"), 65 | # 2021 66 | # 2021 Martin Luther King Day (18th = Monday) 67 | ("2021-01-15", "open"), 68 | ("2021-01-18", "1200"), 69 | ("2021-01-19", "open"), 70 | # 2021 Presidents Day (15th = Monday) 71 | ("2021-02-12", "open"), 72 | ("2021-02-15", "1200"), 73 | ("2021-02-16", "open"), 74 | # 2021 Good Friday (2nd = Friday) 75 | ("2021-04-01", "open"), 76 | ("2021-04-02", "1015"), 77 | ("2021-04-05", "open"), 78 | # 2021 Memorial Day (May 31 = Monday) 79 | ("2021-05-28", "open"), 80 | ("2021-05-31", "1200"), 81 | ("2021-06-01", "open"), 82 | # 2021 Independence Day (4th = Sunday) 83 | ("2021-07-02", "open"), 84 | ("2021-07-05", "1200"), 85 | ("2021-07-06", "open"), 86 | # 2021 Labor Day (6th = Monday) 87 | ("2021-09-03", "open"), 88 | ("2021-09-06", "1200"), 89 | ("2021-09-07", "open"), 90 | # 2021 Thanksgiving (25th = Thursday) 91 | ("2021-11-24", "open"), 92 | ("2021-11-25", "1200"), 93 | ("2021-11-26", "1215"), 94 | # 2021 Christmas (25th = Saturday) 95 | ("2021-12-23", "open"), 96 | ("2021-12-24", "closed"), 97 | ("2021-12-27", "open"), 98 | # 2021/22 New Year's (Dec 31 = Friday) (unusually this period was fully open) 99 | ("2021-12-31", "open"), 100 | ("2022-01-03", "open"), 101 | ("2022-01-03", "open"), 102 | # 2022 103 | # 2022 Martin Luther King Day (17th = Monday) 104 | ("2022-01-14", "open"), 105 | ("2022-01-17", "open"), 106 | ("2022-01-18", "open"), 107 | # 2022 President's Day (21st = Monday) 108 | ("2022-02-18", "open"), 109 | ("2022-02-21", "open"), 110 | ("2022-02-22", "open"), 111 | # 2022 Good Friday (15 = Friday) 112 | ("2022-04-14", "open"), 113 | ("2022-04-15", "closed"), 114 | ("2022-04-18", "open"), 115 | # 2022 Memorial Day (30th = Monday) 116 | ("2022-05-27", "open"), 117 | ("2022-05-30", "open"), 118 | ("2022-05-31", "open"), 119 | # 2022 Juneteenth (20th = Monday) 120 | ("2022-06-17", "open"), 121 | ("2022-06-20", "open"), 122 | ("2022-06-21", "open"), 123 | # 2022 Independence Day (4th = Monday) 124 | ("2022-07-01", "open"), 125 | ("2022-07-04", "open"), 126 | ("2022-07-05", "open"), 127 | # 2022 Labor Day (5th = Monday) 128 | ("2022-09-02", "open"), 129 | ("2022-09-05", "open"), 130 | ("2022-09-06", "open"), 131 | # 2022 Thanksgiving (24th = Thursday) 132 | ("2022-11-23", "open"), 133 | ("2022-11-24", "open"), 134 | ("2022-11-25", "1215"), 135 | ("2022-11-28", "open"), 136 | # 2022 Christmas (25 = Sunday) 137 | ("2022-12-23", "open"), 138 | ("2022-12-26", "closed"), 139 | ("2022-12-27", "open"), 140 | # 2022/23 New Years (Jan 1 = Sunday) 141 | ("2022-12-30", "open"), 142 | ("2023-01-02", "closed"), 143 | ("2023-01-03", "open"), 144 | ("2023-04-07", "1015"), 145 | ], 146 | ids=lambda x: f"{x[0]} {x[1]}", 147 | ) 148 | def test_2020_through_2022_and_prior_holidays(day_status): 149 | day_str = day_status[0] 150 | day_ts = pd.Timestamp(day_str, tz=TZ) 151 | expected_status = day_status[1] 152 | 153 | under_test = CMEGlobexFXExchangeCalendar() 154 | schedule = under_test.schedule("2020-01-01", "2023-04-28", tz=TZ) 155 | 156 | if expected_status == "open": 157 | s = schedule.loc[day_str] 158 | assert s["market_open"] == day_ts + Day(-1) + Hour(17) + Minute(0) 159 | assert s["market_close"] == day_ts + Day(0) + Hour(16) + Minute(0) 160 | elif expected_status == "closed": 161 | assert day_ts.tz_localize(None) not in schedule.index 162 | else: 163 | s = schedule.loc[day_str] 164 | hour = int(expected_status[0:2]) 165 | minute = int(expected_status[2:4]) 166 | assert s["market_open"] == day_ts + Day(-1) + Hour(17) 167 | assert s["market_close"] == day_ts + Day(0) + Hour(hour) + Minute(minute) 168 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/bmf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Quantopian, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import sys 17 | from datetime import time 18 | 19 | from pandas import Timestamp 20 | from pandas.tseries.holiday import ( 21 | AbstractHolidayCalendar, 22 | Day, 23 | Easter, 24 | GoodFriday, 25 | Holiday, 26 | ) 27 | 28 | # check python versiOn aNd import accordingly 29 | if sys.version_info >= (3, 9): 30 | # For Python 3.9 and later, import directly 31 | from zoneinfo import ZoneInfo 32 | else: 33 | # For Python 3.8 and earlier, import from backports 34 | from backports.zoneinfo import ZoneInfo 35 | 36 | from pandas_market_calendars.market_calendar import FRIDAY, MarketCalendar 37 | 38 | # Universal Confraternization (new years day) 39 | ConfUniversal = Holiday( 40 | "Dia da Confraternizacao Universal", 41 | month=1, 42 | day=1, 43 | ) 44 | # Sao Paulo city birthday 45 | AniversarioSaoPaulo = Holiday("Aniversario de Sao Paulo", month=1, day=25, end_date="2021-12-31") 46 | # Carnival Monday 47 | CarnavalSegunda = Holiday("Carnaval Segunda", month=1, day=1, offset=[Easter(), Day(-48)]) 48 | # Carnival Tuesday 49 | CarnavalTerca = Holiday("Carnaval Terca", month=1, day=1, offset=[Easter(), Day(-47)]) 50 | # Ash Wednesday (short day) 51 | QuartaCinzas = Holiday("Quarta Cinzas", month=1, day=1, offset=[Easter(), Day(-46)]) 52 | # Good Friday 53 | SextaPaixao = GoodFriday 54 | # Feast of the Most Holy Body of Christ 55 | CorpusChristi = Holiday("Corpus Christi", month=1, day=1, offset=[Easter(), Day(60)]) 56 | # Tiradentes Memorial 57 | Tiradentes = Holiday( 58 | "Tiradentes", 59 | month=4, 60 | day=21, 61 | ) 62 | # Labor Day 63 | DiaTrabalho = Holiday( 64 | "Dia Trabalho", 65 | month=5, 66 | day=1, 67 | ) 68 | # Constitutionalist Revolution 69 | Constitucionalista = Holiday("Constitucionalista", month=7, day=9, start_date="1997-01-01", end_date="2019-12-31") 70 | # Independence Day 71 | Independencia = Holiday( 72 | "Independencia", 73 | month=9, 74 | day=7, 75 | ) 76 | # Our Lady of Aparecida 77 | Aparecida = Holiday( 78 | "Nossa Senhora de Aparecida", 79 | month=10, 80 | day=12, 81 | ) 82 | # All Souls' Day 83 | Finados = Holiday( 84 | "Dia dos Finados", 85 | month=11, 86 | day=2, 87 | ) 88 | # Proclamation of the Republic 89 | ProclamacaoRepublica = Holiday( 90 | "Proclamacao da Republica", 91 | month=11, 92 | day=15, 93 | ) 94 | # Day of Black Awareness (municipal holiday for the city of São Paulo) 95 | ConscienciaNegra = Holiday( 96 | "Dia da Consciencia Negra", 97 | month=11, 98 | day=20, 99 | start_date="2004-01-01", 100 | end_date="2019-12-31", 101 | ) 102 | # Day of Black Awareness (national holiday) 103 | ConscienciaNegraNacional = Holiday( 104 | "Dia da Consciencia Negra", 105 | month=11, 106 | day=20, 107 | start_date="2023-12-22", 108 | ) 109 | # Christmas Eve 110 | VesperaNatal = Holiday( 111 | "Vespera Natal", 112 | month=12, 113 | day=24, 114 | ) 115 | # Christmas 116 | Natal = Holiday( 117 | "Natal", 118 | month=12, 119 | day=25, 120 | ) 121 | # New Year's Eve 122 | AnoNovo = Holiday( 123 | "Ano Novo", 124 | month=12, 125 | day=31, 126 | ) 127 | # New Year's Eve falls on Saturday 128 | AnoNovoSabado = Holiday( 129 | "Ano Novo Sabado", 130 | month=12, 131 | day=30, 132 | days_of_week=(FRIDAY,), 133 | ) 134 | # New Year's Eve falls on Sunday 135 | AnoNovoDomingo = Holiday( 136 | "Ano Novo Domingo", 137 | month=12, 138 | day=29, 139 | days_of_week=(FRIDAY,), 140 | ) 141 | 142 | ########################## 143 | # Non-recurring holidays 144 | ########################## 145 | 146 | Constitucionalista2021 = Timestamp("2021-07-09", tz="UTC") 147 | ConscienciaNegra2021 = Timestamp("2021-11-20", tz="UTC") 148 | 149 | 150 | class BMFExchangeCalendar(MarketCalendar): 151 | """ 152 | Exchange calendar for BM&F BOVESPA 153 | 154 | Open Time: 10:00 AM, Brazil/Sao Paulo 155 | Close Time: 5:00 PM, Brazil/Sao Paulo 156 | 157 | Regularly-Observed Holidays: 158 | - Universal Confraternization (New year's day, Jan 1) 159 | - Sao Paulo City Anniversary (Jan 25 until 2021) 160 | - Carnaval Monday (48 days before Easter) 161 | - Carnaval Tuesday (47 days before Easter) 162 | - Passion of the Christ (Good Friday, 2 days before Easter) 163 | - Corpus Christi (60 days after Easter) 164 | - Tiradentes (April 21) 165 | - Labor day (May 1) 166 | - Constitutionalist Revolution (July 9 from 1997 until 2021, skipping 2020) 167 | - Independence Day (September 7) 168 | - Our Lady of Aparecida Feast (October 12) 169 | - All Souls' Day (November 2) 170 | - Proclamation of the Republic (November 15) 171 | - Day of Black Awareness, municipal holiday for the city of São Paulo (November 20 from 2004 until 2021, skipping 2020) 172 | - Day of Black Awareness, national holiday (November 20 starting in 2024) 173 | - Christmas (December 24 and 25) 174 | - Friday before New Year's Eve (December 30 or 29 if NYE falls on a Saturday or Sunday, respectively) 175 | - New Year's Eve (December 31) 176 | """ 177 | 178 | aliases = ["BMF", "B3"] 179 | regular_market_times = { 180 | "market_open": ((None, time(10)),), 181 | "market_close": ((None, time(17)),), 182 | } 183 | 184 | @property 185 | def name(self): 186 | return "BMF" 187 | 188 | @property 189 | def tz(self): 190 | return ZoneInfo("America/Sao_Paulo") 191 | 192 | @property 193 | def regular_holidays(self): 194 | return AbstractHolidayCalendar( 195 | rules=[ 196 | ConfUniversal, 197 | AniversarioSaoPaulo, 198 | CarnavalSegunda, 199 | CarnavalTerca, 200 | SextaPaixao, 201 | CorpusChristi, 202 | Tiradentes, 203 | DiaTrabalho, 204 | Constitucionalista, 205 | Independencia, 206 | Aparecida, 207 | Finados, 208 | ProclamacaoRepublica, 209 | ConscienciaNegra, 210 | ConscienciaNegraNacional, 211 | VesperaNatal, 212 | Natal, 213 | AnoNovo, 214 | AnoNovoSabado, 215 | AnoNovoDomingo, 216 | ] 217 | ) 218 | 219 | @property 220 | def adhoc_holidays(self): 221 | return [Constitucionalista2021, ConscienciaNegra2021] 222 | 223 | @property 224 | def special_opens(self): 225 | return [(time(13, 1), AbstractHolidayCalendar(rules=[QuartaCinzas]))] 226 | -------------------------------------------------------------------------------- /docs/pandas_market_calendars.rst: -------------------------------------------------------------------------------- 1 | pandas\_market\_calendars package 2 | ================================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | pandas\_market\_calendars.calendar\_registry module 8 | --------------------------------------------------- 9 | 10 | .. automodule:: pandas_market_calendars.calendar_registry 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pandas\_market\_calendars.calendar\_utils module 16 | ------------------------------------------------ 17 | 18 | .. automodule:: pandas_market_calendars.calendar_utils 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pandas\_market\_calendars.class\_registry module 24 | ------------------------------------------------ 25 | 26 | .. automodule:: pandas_market_calendars.class_registry 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pandas\_market\_calendars.exchange\_calendar\_asx module 32 | -------------------------------------------------------- 33 | 34 | .. automodule:: pandas_market_calendars.exchange_calendar_asx 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pandas\_market\_calendars.exchange\_calendar\_bmf module 40 | -------------------------------------------------------- 41 | 42 | .. automodule:: pandas_market_calendars.exchange_calendar_bmf 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | pandas\_market\_calendars.exchange\_calendar\_cfe module 48 | -------------------------------------------------------- 49 | 50 | .. automodule:: pandas_market_calendars.exchange_calendar_cfe 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | pandas\_market\_calendars.exchange\_calendar\_cme module 56 | -------------------------------------------------------- 57 | 58 | .. automodule:: pandas_market_calendars.exchange_calendar_cme 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | pandas\_market\_calendars.exchange\_calendar\_eurex module 64 | ---------------------------------------------------------- 65 | 66 | .. automodule:: pandas_market_calendars.exchange_calendar_eurex 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | pandas\_market\_calendars.exchange\_calendar\_hkex module 72 | --------------------------------------------------------- 73 | 74 | .. automodule:: pandas_market_calendars.exchange_calendar_hkex 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | pandas\_market\_calendars.exchange\_calendar\_ice module 80 | -------------------------------------------------------- 81 | 82 | .. automodule:: pandas_market_calendars.exchange_calendar_ice 83 | :members: 84 | :undoc-members: 85 | :show-inheritance: 86 | 87 | pandas\_market\_calendars.exchange\_calendar\_jpx module 88 | -------------------------------------------------------- 89 | 90 | .. automodule:: pandas_market_calendars.exchange_calendar_jpx 91 | :members: 92 | :undoc-members: 93 | :show-inheritance: 94 | 95 | pandas\_market\_calendars.exchange\_calendar\_lse module 96 | -------------------------------------------------------- 97 | 98 | .. automodule:: pandas_market_calendars.exchange_calendar_lse 99 | :members: 100 | :undoc-members: 101 | :show-inheritance: 102 | 103 | pandas\_market\_calendars.exchange\_calendar\_nyse module 104 | --------------------------------------------------------- 105 | 106 | .. automodule:: pandas_market_calendars.exchange_calendar_nyse 107 | :members: 108 | :undoc-members: 109 | :show-inheritance: 110 | 111 | pandas\_market\_calendars.exchange\_calendar\_ose module 112 | -------------------------------------------------------- 113 | 114 | .. automodule:: pandas_market_calendars.exchange_calendar_ose 115 | :members: 116 | :undoc-members: 117 | :show-inheritance: 118 | 119 | pandas\_market\_calendars.exchange\_calendar\_six module 120 | -------------------------------------------------------- 121 | 122 | .. automodule:: pandas_market_calendars.exchange_calendar_six 123 | :members: 124 | :undoc-members: 125 | :show-inheritance: 126 | 127 | pandas\_market\_calendars.exchange\_calendar\_sse module 128 | -------------------------------------------------------- 129 | 130 | .. automodule:: pandas_market_calendars.exchange_calendar_sse 131 | :members: 132 | :undoc-members: 133 | :show-inheritance: 134 | 135 | pandas\_market\_calendars.exchange\_calendar\_tase module 136 | --------------------------------------------------------- 137 | 138 | .. automodule:: pandas_market_calendars.exchange_calendar_tase 139 | :members: 140 | :undoc-members: 141 | :show-inheritance: 142 | 143 | pandas\_market\_calendars.exchange\_calendar\_tsx module 144 | -------------------------------------------------------- 145 | 146 | .. automodule:: pandas_market_calendars.exchange_calendar_tsx 147 | :members: 148 | :undoc-members: 149 | :show-inheritance: 150 | 151 | pandas\_market\_calendars.exchange\_calendar\_xbom module 152 | --------------------------------------------------------- 153 | 154 | .. automodule:: pandas_market_calendars.exchange_calendar_xbom 155 | :members: 156 | :undoc-members: 157 | :show-inheritance: 158 | 159 | pandas\_market\_calendars.holidays\_cn module 160 | --------------------------------------------- 161 | 162 | .. automodule:: pandas_market_calendars.holidays_cn 163 | :members: 164 | :undoc-members: 165 | :show-inheritance: 166 | 167 | pandas\_market\_calendars.holidays\_jp module 168 | --------------------------------------------- 169 | 170 | .. automodule:: pandas_market_calendars.holidays_jp 171 | :members: 172 | :undoc-members: 173 | :show-inheritance: 174 | 175 | pandas\_market\_calendars.holidays\_oz module 176 | --------------------------------------------- 177 | 178 | .. automodule:: pandas_market_calendars.holidays_oz 179 | :members: 180 | :undoc-members: 181 | :show-inheritance: 182 | 183 | pandas\_market\_calendars.holidays\_uk module 184 | --------------------------------------------- 185 | 186 | .. automodule:: pandas_market_calendars.holidays_uk 187 | :members: 188 | :undoc-members: 189 | :show-inheritance: 190 | 191 | pandas\_market\_calendars.holidays\_us module 192 | --------------------------------------------- 193 | 194 | .. automodule:: pandas_market_calendars.holidays_us 195 | :members: 196 | :undoc-members: 197 | :show-inheritance: 198 | 199 | pandas\_market\_calendars.jpx\_equinox module 200 | --------------------------------------------- 201 | 202 | .. automodule:: pandas_market_calendars.jpx_equinox 203 | :members: 204 | :undoc-members: 205 | :show-inheritance: 206 | 207 | pandas\_market\_calendars.market\_calendar module 208 | ------------------------------------------------- 209 | 210 | .. automodule:: pandas_market_calendars.market_calendar 211 | :members: 212 | :undoc-members: 213 | :show-inheritance: 214 | 215 | pandas\_market\_calendars.trading\_calendars\_mirror module 216 | ----------------------------------------------------------- 217 | 218 | .. automodule:: pandas_market_calendars.trading_calendars_mirror 219 | :members: 220 | :undoc-members: 221 | :show-inheritance: 222 | 223 | Module contents 224 | --------------- 225 | 226 | .. automodule:: pandas_market_calendars 227 | :members: 228 | :undoc-members: 229 | :show-inheritance: 230 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_energy_and_metals.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Quantopian, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import sys 17 | from datetime import time 18 | 19 | from pandas.tseries.holiday import ( 20 | AbstractHolidayCalendar, 21 | ) # , GoodFriday, USLaborDay, USPresidentsDay, USThanksgivingDay 22 | 23 | # check python versiOn aNd import accordingly 24 | if sys.version_info >= (3, 9): 25 | # For Python 3.9 and later, import directly 26 | from zoneinfo import ZoneInfo 27 | else: 28 | # For Python 3.8 and earlier, import from backports 29 | from backports.zoneinfo import ZoneInfo 30 | 31 | from pandas_market_calendars.holidays.cme_globex import ( 32 | USMartinLutherKingJrFrom2022, 33 | USMartinLutherKingJrPre2022, 34 | USNewYearsDay, 35 | USPresidentsDayFrom2022, 36 | USPresidentsDayPre2022, 37 | GoodFriday, 38 | USMemorialDayFrom2022, 39 | USMemorialDayPre2022, 40 | USJuneteenthFrom2022, 41 | USIndependenceDayFrom2022, 42 | USIndependenceDayPre2022, 43 | USLaborDay, 44 | USThanksgivingDayFrom2022, 45 | USThanksgivingDayPre2022, 46 | FridayAfterThanksgiving, 47 | ChristmasCME, 48 | ) 49 | from .cme_globex_base import CMEGlobexBaseExchangeCalendar 50 | 51 | 52 | # from .holidays_us import (Christmas, ChristmasEveBefore1993, ChristmasEveInOrAfter1993, USBlackFridayInOrAfter1993, 53 | # USIndependenceDay, USMartinLutherKingJrAfter1998, USMemorialDay, USJuneteenthAfter2022, 54 | # USNationalDaysofMourning, USNewYearsDay) 55 | 56 | 57 | class CMEGlobexEnergyAndMetalsExchangeCalendar(CMEGlobexBaseExchangeCalendar): 58 | """ 59 | Exchange calendar for CME for Energy and Metals products 60 | 61 | Both follow the same trading/holiday schedule 62 | 63 | NOT IMPLEMENTED: Dubai Mercantile Exchange (DME) follows this schedule but with holiday exceptions. 64 | 65 | Energy Products: 66 | Crude and Refined: https://www.cmegroup.com/trading/energy/crude-and-refined-products.html 67 | - HO NY Harbor ULSD Futures 68 | - CL Crude Oil Futures 69 | - RB RBOB Gasoline Futures 70 | - MCL Micro WTI Crude Oil Futures 71 | Natural Gas 72 | - NG Henry Hub Natural Gas Futures 73 | - TTF Dutch TTF Natural Gas Calendar Month Futures 74 | - NN Henry Hub Natural Gas Last Day Financial Futures 75 | Voluntary Carbon Emissions Offset Futures 76 | - CGO CBL Core Global Emmissions Offset (C-GEO) Futures 77 | - NGO CBL Nature-based Global Emissionns Offset Futures 78 | - GEO CBL Global Emissions Offset Futures 79 | 80 | Metals Products: https://www.cmegroup.com/markets/metals.html 81 | Precious Metals 82 | - GC Gold Futures 83 | - SI Silver Futures 84 | - PL Platinum Futures 85 | Base Metals 86 | - HG Copper Futures 87 | - ALI Aluminum Futures 88 | - QC E-mini Copper Futures 89 | Ferrous Metals 90 | - HRC U.S. Midwest Domestic Hot-Rolled Coil Steel (CRU) Index Futures 91 | - BUS U.S. Midwest Busheling Ferrous Scrap (AMM) Futures 92 | - TIO Iron Ore 62% Fe, CFR China (Platts) Futures 93 | 94 | Sample GLOBEX Trading Times 95 | https://www.cmegroup.com/markets/energy/crude-oil/light-sweet-crude.contractSpecs.html 96 | Sunday - Friday: 5:00pm - 4:00 pm CT 97 | 98 | Calendar: http://www.cmegroup.com/tools-information/holiday-calendar.html 99 | """ 100 | 101 | aliases = [ 102 | "CMEGlobex_EnergyAndMetals", 103 | "CMEGlobex_Energy", 104 | "CMEGlobex_CrudeAndRefined", 105 | "CMEGlobex_NYHarbor", 106 | "CMEGlobex_HO", 107 | "HO", 108 | "CMEGlobex_Crude", 109 | "CMEGlobex_CL", 110 | "CL", 111 | "CMEGlobex_Gas", 112 | "CMEGlobex_RB", 113 | "RB", 114 | "CMEGlobex_MicroCrude", 115 | "CMEGlobex_MCL", 116 | "MCL", 117 | "CMEGlobex_NatGas", 118 | "CMEGlobex_NG", 119 | "NG", 120 | "CMEGlobex_Dutch_NatGas", 121 | "CMEGlobex_TTF", 122 | "TTF", 123 | "CMEGlobex_LastDay_NatGas", 124 | "CMEGlobex_NN", 125 | "NN", 126 | "CMEGlobex_CarbonOffset", 127 | "CMEGlobex_CGO", 128 | "CGO", 129 | "C-GEO", 130 | "CMEGlobex_NGO", 131 | "NGO", 132 | "CMEGlobex_GEO", 133 | "GEO", 134 | "CMEGlobex_Metals", 135 | "CMEGlobex_PreciousMetals", 136 | "CMEGlobex_Gold", 137 | "CMEGlobex_GC", 138 | "GC", 139 | "CMEGlobex_Silver", 140 | "CMEGlobex_SI", 141 | "SI", 142 | "CMEGlobex_Platinum", 143 | "CMEGlobex_PL", 144 | "PL", 145 | "CMEGlobex_BaseMetals", 146 | "CMEGlobex_Copper", 147 | "CMEGlobex_HG", 148 | "HG", 149 | "CMEGlobex_Aluminum", 150 | "CMEGlobex_ALI", 151 | "ALI", 152 | "CMEGlobex_Copper", 153 | "CMEGlobex_QC", 154 | "QC", 155 | "CMEGlobex_FerrousMetals", 156 | "CMEGlobex_HRC", 157 | "HRC", 158 | "CMEGlobex_BUS", 159 | "BUS", 160 | "CMEGlobex_TIO", 161 | "TIO", 162 | ] 163 | 164 | regular_market_times = { 165 | "market_open": ((None, time(17), -1),), # Sunday offset. Central Timezone (CT) 166 | "market_close": ((None, time(16)),), 167 | } 168 | 169 | @property 170 | def name(self): 171 | return "CMEGlobex_EnergyAndMetals" 172 | 173 | @property 174 | def regular_holidays(self): 175 | return AbstractHolidayCalendar( 176 | rules=[ 177 | USNewYearsDay, 178 | GoodFriday, 179 | ChristmasCME, 180 | ] 181 | ) 182 | 183 | # @property 184 | # def adhoc_holidays(self): 185 | # return USNationalDaysofMourning 186 | 187 | @property 188 | def special_closes(self): 189 | return [ 190 | ( 191 | time(12, tzinfo=ZoneInfo("America/Chicago")), 192 | AbstractHolidayCalendar( 193 | rules=[ 194 | USMartinLutherKingJrPre2022, 195 | USPresidentsDayPre2022, 196 | USMemorialDayPre2022, 197 | USIndependenceDayPre2022, 198 | USLaborDay, 199 | USThanksgivingDayPre2022, 200 | ] 201 | ), 202 | ), 203 | ( 204 | time(12, 45, tzinfo=ZoneInfo("America/Chicago")), 205 | AbstractHolidayCalendar( 206 | rules=[ 207 | FridayAfterThanksgiving, 208 | ] 209 | ), 210 | ), 211 | ( 212 | time(13, 30, tzinfo=ZoneInfo("America/Chicago")), 213 | AbstractHolidayCalendar( 214 | rules=[ 215 | USMartinLutherKingJrFrom2022, 216 | USPresidentsDayFrom2022, 217 | USMemorialDayFrom2022, 218 | USJuneteenthFrom2022, 219 | USIndependenceDayFrom2022, 220 | USThanksgivingDayFrom2022, 221 | ] 222 | ), 223 | ), 224 | ] 225 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pandas_market_calendars 2 | ======================= 3 | Market calendars to use with pandas for trading applications. 4 | 5 | .. image:: https://badge.fury.io/py/pandas-market-calendars.svg 6 | :target: https://badge.fury.io/py/pandas-market-calendars 7 | 8 | .. image:: https://readthedocs.org/projects/pandas-market-calendars/badge/?version=latest 9 | :target: http://pandas-market-calendars.readthedocs.io/en/latest/?badge=latest 10 | :alt: Documentation Status 11 | 12 | .. image:: https://coveralls.io/repos/github/rsheftel/pandas_market_calendars/badge.svg?branch=master 13 | :target: https://coveralls.io/github/rsheftel/pandas_market_calendars?branch=master 14 | 15 | Documentation 16 | ------------- 17 | http://pandas-market-calendars.readthedocs.io/en/latest/ 18 | 19 | Overview 20 | -------- 21 | The Pandas package is widely used in finance and specifically for time series analysis. It includes excellent 22 | functionality for generating sequences of dates and capabilities for custom holiday calendars, but as an explicit 23 | design choice it does not include the actual holiday calendars for specific exchanges or OTC markets. 24 | 25 | The pandas_market_calendars package looks to fill that role with the holiday, late open and early close calendars 26 | for specific exchanges and OTC conventions. pandas_market_calendars also adds several functions to manipulate the 27 | market calendars and includes a date_range function to create a pandas DatetimeIndex including only the datetimes 28 | when the markets are open. Additionally the package contains product specific calendars for future exchanges which 29 | have different market open, closes, breaks and holidays based on product type. 30 | 31 | This package provides access to over 50+ unique exchange calendars for global equity and futures markets. 32 | 33 | This package is a fork of the Zipline package from Quantopian and extracts just the relevant parts. All credit for 34 | their excellent work to Quantopian. 35 | 36 | Major Releases 37 | ~~~~~~~~~~~~~~ 38 | As of v1.0 this package only works with Python3. This is consistent with Pandas dropping support for Python2. 39 | 40 | As of v1.4 this package now has the concept of a break during the trading day. For example this can accommodate Asian 41 | markets that have a lunch break, or futures markets that are open 24 hours with a break in the day for trade processing. 42 | 43 | As of v2.0 this package provides a mirror of all the calendars from the `exchange_calendars `_ 44 | package, which itself is the now maintained fork of the original trading_calendars package. This adds over 50 calendars. 45 | 46 | As of v3.0, the function date_range() is more complete and consistent, for more discussion on the topic refer to PR #142 and Issue #138. 47 | 48 | As of v4.0, this package provides the framework to add interruptions to calendars. These can also be added to a schedule and viewed using 49 | the new interruptions_df property. A full list of changes can be found in PR #210. 50 | 51 | As of v5.0, this package uses the new zoneinfo standard to timezones and depricates and removes pytz. Minimum python version is now 3.9 52 | 53 | Source location 54 | ~~~~~~~~~~~~~~~ 55 | Hosted on GitHub: https://github.com/rsheftel/pandas_market_calendars 56 | 57 | Installation 58 | ~~~~~~~~~~~~ 59 | ``pip install pandas_market_calendars`` 60 | 61 | Arch Linux package available here: https://aur.archlinux.org/packages/python-pandas_market_calendars/ 62 | 63 | Calendars 64 | --------- 65 | The list of `available calendars `_ 66 | 67 | Quick Start 68 | ----------- 69 | .. code:: python 70 | 71 | import pandas_market_calendars as mcal 72 | 73 | # Create a calendar 74 | nyse = mcal.get_calendar('NYSE') 75 | 76 | # Show available calendars 77 | print(mcal.get_calendar_names()) 78 | 79 | .. code:: python 80 | 81 | early = nyse.schedule(start_date='2012-07-01', end_date='2012-07-10') 82 | early 83 | 84 | 85 | .. parsed-literal:: 86 | 87 | market_open market_close 88 | =========== ========================= ========================= 89 | 2012-07-02 2012-07-02 13:30:00+00:00 2012-07-02 20:00:00+00:00 90 | 2012-07-03 2012-07-03 13:30:00+00:00 2012-07-03 17:00:00+00:00 91 | 2012-07-05 2012-07-05 13:30:00+00:00 2012-07-05 20:00:00+00:00 92 | 2012-07-06 2012-07-06 13:30:00+00:00 2012-07-06 20:00:00+00:00 93 | 2012-07-09 2012-07-09 13:30:00+00:00 2012-07-09 20:00:00+00:00 94 | 2012-07-10 2012-07-10 13:30:00+00:00 2012-07-10 20:00:00+00:00 95 | 96 | 97 | .. code:: python 98 | 99 | mcal.date_range(early, frequency='1D') 100 | 101 | 102 | 103 | 104 | .. parsed-literal:: 105 | 106 | DatetimeIndex(['2012-07-02 20:00:00+00:00', '2012-07-03 17:00:00+00:00', 107 | '2012-07-05 20:00:00+00:00', '2012-07-06 20:00:00+00:00', 108 | '2012-07-09 20:00:00+00:00', '2012-07-10 20:00:00+00:00'], 109 | dtype='datetime64[ns, UTC]', freq=None) 110 | 111 | 112 | 113 | .. code:: python 114 | 115 | mcal.date_range(early, frequency='1H') 116 | 117 | 118 | 119 | 120 | .. parsed-literal:: 121 | 122 | DatetimeIndex(['2012-07-02 14:30:00+00:00', '2012-07-02 15:30:00+00:00', 123 | '2012-07-02 16:30:00+00:00', '2012-07-02 17:30:00+00:00', 124 | '2012-07-02 18:30:00+00:00', '2012-07-02 19:30:00+00:00', 125 | '2012-07-02 20:00:00+00:00', '2012-07-03 14:30:00+00:00', 126 | '2012-07-03 15:30:00+00:00', '2012-07-03 16:30:00+00:00', 127 | '2012-07-03 17:00:00+00:00', '2012-07-05 14:30:00+00:00', 128 | '2012-07-05 15:30:00+00:00', '2012-07-05 16:30:00+00:00', 129 | '2012-07-05 17:30:00+00:00', '2012-07-05 18:30:00+00:00', 130 | '2012-07-05 19:30:00+00:00', '2012-07-05 20:00:00+00:00', 131 | '2012-07-06 14:30:00+00:00', '2012-07-06 15:30:00+00:00', 132 | '2012-07-06 16:30:00+00:00', '2012-07-06 17:30:00+00:00', 133 | '2012-07-06 18:30:00+00:00', '2012-07-06 19:30:00+00:00', 134 | '2012-07-06 20:00:00+00:00', '2012-07-09 14:30:00+00:00', 135 | '2012-07-09 15:30:00+00:00', '2012-07-09 16:30:00+00:00', 136 | '2012-07-09 17:30:00+00:00', '2012-07-09 18:30:00+00:00', 137 | '2012-07-09 19:30:00+00:00', '2012-07-09 20:00:00+00:00', 138 | '2012-07-10 14:30:00+00:00', '2012-07-10 15:30:00+00:00', 139 | '2012-07-10 16:30:00+00:00', '2012-07-10 17:30:00+00:00', 140 | '2012-07-10 18:30:00+00:00', '2012-07-10 19:30:00+00:00', 141 | '2012-07-10 20:00:00+00:00'], 142 | dtype='datetime64[ns, UTC]', freq=None) 143 | 144 | Contributing 145 | ------------ 146 | All improvements and additional (and corrections) in the form of pull requests are welcome. This package will grow in 147 | value and correctness the more eyes are on it. 148 | 149 | To add new functionality please include tests which are in standard pytest format. 150 | 151 | Use pytest to run the test suite. 152 | 153 | Use black for formatting. 154 | 155 | For complete information on contributing see CONTRIBUTING.md_ 156 | 157 | .. _CONTRIBUTING.md: https://github.com/rsheftel/pandas_market_calendars/blob/master/CONTRIBUTING.md 158 | 159 | Future 160 | ------ 161 | This package is open sourced under the MIT license. Everyone is welcome to add more exchanges or OTC markets, confirm 162 | or correct the existing calendars, and generally do whatever they desire with this code. 163 | 164 | Sponsor 165 | ------- 166 | .. image:: https://www.tradinghours.com/img/logo-with-words.png 167 | :target: https://www.tradinghours.com/data 168 | :alt: TradingHours.com 169 | 170 | `TradingHours.com `_ provides the most accurate and comprehensive coverage of market holidays and trading hours data available. They cover over 1,100 markets worldwide, with extensive historical data and full coverage of all global trading venues, including the CME, ICE, Eurex, and more. 171 | 172 | Their data is continuously monitored for changes and updated daily. If there's a market you need that they don't currently cover, they'll add it. For when accurate, reliable data matters most, choose TradingHours.com. `Learn more `_ 173 | --------------------------------------------------------------------------------