├── .git-blame-ignore-revs ├── .github ├── config_new_release.yml └── workflows │ ├── check_branch.yml │ ├── releaser.yml │ ├── test_release.yml │ └── test_runner.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.rst ├── coverage.sh ├── docs ├── Makefile ├── calendars.rst ├── change_log.rst ├── conf.py ├── index.rst ├── make.bat ├── make_docs.bat ├── make_examples.bat ├── modules.rst ├── new_market.rst ├── pandas_market_calendars.rst ├── requirements.txt └── usage.rst ├── examples └── usage.ipynb ├── pandas_market_calendars ├── __init__.py ├── calendar_registry.py ├── calendar_utils.py ├── calendars │ ├── __init__.py │ ├── asx.py │ ├── bmf.py │ ├── bse.py │ ├── cboe.py │ ├── cme.py │ ├── cme_globex_agriculture.py │ ├── cme_globex_base.py │ ├── cme_globex_crypto.py │ ├── cme_globex_energy_and_metals.py │ ├── cme_globex_equities.py │ ├── cme_globex_fixed_income.py │ ├── cme_globex_fx.py │ ├── eurex.py │ ├── eurex_fixed_income.py │ ├── hkex.py │ ├── ice.py │ ├── iex.py │ ├── jpx.py │ ├── lse.py │ ├── mirror.py │ ├── nyse.py │ ├── ose.py │ ├── sifma.py │ ├── six.py │ ├── sse.py │ ├── tase.py │ └── tsx.py ├── class_registry.py ├── holidays │ ├── __init__.py │ ├── cme.py │ ├── cme_globex.py │ ├── cn.py │ ├── jp.py │ ├── jpx_equinox.py │ ├── nyse.py │ ├── oz.py │ ├── sifma.py │ ├── uk.py │ └── us.py └── market_calendar.py ├── pyproject.toml └── tests ├── __init__.py ├── data ├── jpx_open_weekdays_since_1949.csv └── nyse_all_full_day_holidays_since_1928.csv ├── test_24_7_calendar.py ├── test_XNYS_calendar.py ├── test_asx_calendar.py ├── test_bmf_calendar.py ├── test_bse_calendar.py ├── test_cboe_calendars.py ├── test_class_registry.py ├── test_cme_agriculture_calendar.py ├── test_cme_bond_calendar.py ├── test_cme_equity_calendar.py ├── test_date_range.py ├── test_eurex_calendar.py ├── test_eurex_fixed_income_calendar.py ├── test_exchange_calendar_cme_globex_crypto.py ├── test_exchange_calendar_cme_globex_energy_and_metals.py ├── test_exchange_calendar_cme_globex_equities.py ├── test_exchange_calendar_cme_globex_fixed_income.py ├── test_exchange_calendar_cme_globex_fx.py ├── test_exchange_calendar_cme_globex_grains.py ├── test_hkex_calendar.py ├── test_ice_calendar.py ├── test_iex_calendar.py ├── test_jpx_calendar.py ├── test_lse_calendar.py ├── test_market_calendar.py ├── test_nyse_calendar.py ├── test_nyse_calendar_early_years.py ├── test_ose_calendar.py ├── test_sifma_calendars.py ├── test_six_calendar.py ├── test_sse_calendar.py ├── test_tsx_calendar.py ├── test_utils.py └── test_xtae_calendar.py /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | fd632acb99b20dc7c8ed2ab03e35fccdd3deed70 -------------------------------------------------------------------------------- /.github/config_new_release.yml: -------------------------------------------------------------------------------- 1 | new_version: '4.4.0' 2 | 3 | change_log: | 4 | - Now works with pandas 2.2.0 5 | 6 | release_body: | 7 | 8 | 9 | Full Documentation: pandas_market_calendars.readthedocs.io 10 | 11 | -------------------------------------------------------------------------------- /.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 | 20 | -------------------------------------------------------------------------------- /.github/workflows/releaser.yml: -------------------------------------------------------------------------------- 1 | name: releaser 2 | on: 3 | push: 4 | branches: [ master ] 5 | 6 | env: 7 | NEWV: '' 8 | OLDV: '' 9 | 10 | jobs: 11 | run_tests: 12 | uses: ./.github/workflows/test_runner.yml 13 | 14 | make_new_release: 15 | needs: run_tests 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: get new_version 21 | id: new_version 22 | uses: KJ002/read-yaml@1.6 23 | with: 24 | file: .github/config_new_release.yml 25 | key-path: '["new_version"]' 26 | 27 | - name: get_new_current_version 28 | run: | 29 | echo "NEWV=${{ steps.new_version.outputs.data }}" >> $GITHUB_ENV 30 | echo "OLDV=$(grep "VERSION" -m1 pyproject.toml | cut -d"=" -f2 | sed "s/['\" ]//g")" >> $GITHUB_ENV 31 | 32 | - name: verify ${{env.NEWV}} > ${{env.OLDV}} 33 | if: ${{ env.NEWV <= env.OLDV }} 34 | run: echo you did not increment the version number && exit 1 35 | 36 | - name: get changes 37 | id: changes 38 | uses: KJ002/read-yaml@1.6 39 | with: 40 | file: .github/config_new_release.yml 41 | key-path: '["change_log"]' 42 | 43 | - name: get body 44 | id: body 45 | uses: KJ002/read-yaml@1.6 46 | with: 47 | file: .github/config_new_release.yml 48 | key-path: '["release_body"]' 49 | 50 | - name: update change log 51 | run: | 52 | sed 5q docs/change_log.rst > docs/new_log.rst 53 | echo "$NEWV ($(date +%m/%d/%Y))" >> docs/new_log.rst 54 | echo '~~~~~~~~~~~~~~' >> docs/new_log.rst 55 | echo "${{ steps.changes.outputs.data }}" >> docs/new_log.rst 56 | 57 | sed -n 6,$(wc -l docs/change_log.rst | cut -d' ' -f1)p docs/change_log.rst >> docs/new_log.rst 58 | mv docs/new_log.rst docs/change_log.rst 59 | 60 | - name: set up new version 61 | run: | 62 | sed -i "s/$OLDV/$NEWV/" pyproject.toml 63 | 64 | - name: Set up Python 65 | uses: actions/setup-python@v4 66 | with: 67 | python-version: "3.12" 68 | 69 | - name: install dependencies 70 | run: | 71 | python -m pip install --upgrade pip 72 | pip install build twine jupyter pytest 73 | 74 | - name: build package 75 | run: python -m build 76 | 77 | - uses: pypa/gh-action-pypi-publish@release/v1 78 | with: 79 | user: __token__ 80 | password: ${{ secrets.PYPI_TOKEN }} 81 | 82 | - name: install from pypi 83 | run: | 84 | while [ "$NEWV" != $(pip index versions pandas_market_calendars | cut -d'(' -f2 | cut -d')' -f1 | sed 1q) ];\ 85 | do echo not found yet, sleeping 5s; sleep 5s; done 86 | pip install pandas_market_calendars==$NEWV 87 | 88 | - name: run tests 89 | run: | 90 | pip install . 91 | mv pandas_market_calendars pandas_market_calendars_copy 92 | python -c 'import pandas_market_calendars;print(pandas_market_calendars.__version__)' 93 | pytest tests 94 | 95 | - name: prepare usage.rst 96 | run: | 97 | sudo apt install pandoc 98 | jupyter nbconvert --execute --ExecutePreprocessor.kernel_name='python3' --to rst --output-dir docs --output usage.rst examples/usage.ipynb 99 | 100 | - uses: stefanzweifel/git-auto-commit-action@v4 101 | with: 102 | file_pattern: pyproject.toml docs/change_log.rst docs/usage.rst 103 | commit_message: '[GH-Actions] v${{ env.NEWV }} -- updated configuration and documentation files.' 104 | 105 | - name: Create Release 106 | uses: softprops/action-gh-release@v1 107 | with: 108 | name: v${{ env.NEWV }} 109 | tag_name: v${{ env.NEWV }} 110 | body: | 111 | Changes: 112 | ${{ steps.changes.outputs.data }} 113 | 114 | ${{ steps.body.outputs.data }} 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /.github/workflows/test_release.yml: -------------------------------------------------------------------------------- 1 | name: test_release 2 | on: 3 | pull_request: 4 | branches: [ master ] 5 | 6 | workflow_dispatch: 7 | 8 | env: 9 | NEWV: "" 10 | OLDV: "" 11 | 12 | jobs: 13 | check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - if: ${{ github.event.pull_request.head.repo.fork || github.head_ref != 'dev' }} 17 | run: echo PR against main that is not based on dev && exit 1 18 | 19 | run_tests: 20 | needs: check 21 | uses: ./.github/workflows/test_runner.yml 22 | 23 | # test_new_release: 24 | # needs: run_tests 25 | # runs-on: ubuntu-latest 26 | # 27 | # steps: 28 | # - uses: actions/checkout@v3 29 | # 30 | # - name: get new_version 31 | # id: new_version 32 | # uses: KJ002/read-yaml@1.6 33 | # with: 34 | # file: .github/config_new_release.yml 35 | # key-path: '["new_version"]' 36 | # 37 | # - name: get_new_current_version 38 | # run: | 39 | # echo "NEWV=${{ steps.new_version.outputs.data }}" >> $GITHUB_ENV 40 | # echo "OLDV=$(grep "VERSION" -m1 pyproject.toml | cut -d"=" -f2 | sed "s/['\" ]//g")" >> $GITHUB_ENV 41 | # 42 | # - name: warn_no_version 43 | # if: ${{ env.NEWV <= env.OLDV }} 44 | # uses: thollander/actions-comment-pull-request@v1 45 | # with: 46 | # message: WARNING - Version number in change_log is not incremented. Will not test release. 47 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | # 49 | # - name: exit_no_version 50 | # if: ${{ env.NEWV <= env.OLDV }} 51 | # run: echo you did not update the change_log && exit 1 52 | # 53 | # - name: make rc vnumber 54 | # run: echo "NEWV=${{env.NEWV}}rc${{github.run_number}}.dev${{github.run_attempt}}" >> $GITHUB_ENV 55 | # 56 | # - name: set version 57 | # run: | 58 | # echo release candidate: $NEWV 59 | # sed -i "s/$OLDV/$NEWV/" pyproject.toml 60 | # 61 | # - name: Set up Python 62 | # uses: actions/setup-python@v4 63 | # with: 64 | # python-version: "3.12" 65 | # 66 | # - name: Install dependencies 67 | # run: | 68 | # pip install --upgrade pip 69 | # pip install build twine pytest 70 | # 71 | # - name: Build source distribution and wheel files 72 | # run: python -m build 73 | # 74 | # - name: Upload files to TestPyPI 75 | # run: python -m twine upload --verbose --repository testpypi dist/* -u__token__ -p${{ secrets.TEST_PYPI_TOKEN }} 76 | # 77 | # - name: Install from testpypi 78 | # run: | 79 | # while [ "$NEWV" != $(pip index versions -i https://test.pypi.org/simple --pre pandas_market_calendars | cut -d'(' -f2 | cut -d')' -f1 | sed 1q) ];\ 80 | # do echo not found yet, sleeping 5s; sleep 5s; done 81 | # pip install -i https://test.pypi.org/simple pandas_market_calendars==$NEWV --no-deps 82 | # 83 | # - name: test new release 84 | # run: | 85 | # pip install . 86 | # mv pandas_market_calendars pandas_market_calendars_copy 87 | # python -c 'import pandas_market_calendars;print(pandas_market_calendars.__version__)' 88 | # pytest tests 89 | -------------------------------------------------------------------------------- /.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 | jobs: 9 | run_tests: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | os: [ ubuntu-latest, windows-latest, macos-latest ] 14 | python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13'] 15 | 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-python@v4 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | 25 | - name: install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install pytest coveralls black===24.3.0 29 | pip install . 30 | 31 | # - name: ensure black format 32 | # run: python -m black --check pandas_market_calendars tests 33 | 34 | - name: run tests 35 | run: | 36 | coverage run --source=pandas_market_calendars -m pytest tests 37 | coverage lcov -o coverage.lcov 38 | 39 | - name: coveralls parallel 40 | uses: coverallsapp/github-action@master 41 | with: 42 | github-token: ${{ secrets.github_token }} 43 | flag-name: test_${{matrix.os}}_${{matrix.python-version}} 44 | parallel: true 45 | path-to-lcov: coverage.lcov 46 | 47 | report_coverage: 48 | if: ${{ always() }} 49 | needs: run_tests 50 | runs-on: ubuntu-latest 51 | 52 | steps: 53 | - uses: actions/checkout@v3 54 | 55 | - name: coveralls finalize 56 | id: coveralls_finalize 57 | uses: coverallsapp/github-action@master 58 | with: 59 | github-token: ${{secrets.github_token}} 60 | parallel-finished: true 61 | path-to-lcov: coverage.lcov 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /.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 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # VSCode 105 | .vscode/* 106 | *.code-workspace 107 | .history/ 108 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/psf/black 5 | rev: 24.1.1 6 | hooks: 7 | - id: black 8 | # It is recommended to specify the latest version of Python 9 | # supported by your project here, or alternatively use 10 | # pre-commit's default_language_version, see 11 | # https://pre-commit.com/#top_level-default_language_version 12 | language_version: python3.12 13 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | For complete information on contributing see CONTRIBUTING.md_ 154 | 155 | .. _CONTRIBUTING.md: https://github.com/rsheftel/pandas_market_calendars/blob/master/CONTRIBUTING.md 156 | 157 | Future 158 | ------ 159 | This package is open sourced under the MIT license. Everyone is welcome to add more exchanges or OTC markets, confirm 160 | or correct the existing calendars, and generally do whatever they desire with this code. 161 | 162 | Sponsor 163 | ------- 164 | .. image:: https://www.tradinghours.com/img/logo-with-words.png 165 | :target: https://www.tradinghours.com/data 166 | :alt: TradingHours.com 167 | 168 | `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. 169 | 170 | 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 `_ 171 | -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This only works in Cygwin on Windows 4 | 5 | py3 -m pytest --color=yes --cov=pandas_market_calendars --cov-report html 6 | cygstart htmlcov/index.html 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | pandas_market_calendars 2 | ======================= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | pandas_market_calendars 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendar_registry.py: -------------------------------------------------------------------------------- 1 | from .market_calendar import MarketCalendar 2 | from .calendars.asx import ASXExchangeCalendar 3 | from .calendars.bmf import BMFExchangeCalendar 4 | from .calendars.bse import BSEExchangeCalendar 5 | from .calendars.cboe import CFEExchangeCalendar 6 | from .calendars.cme import CMEEquityExchangeCalendar, CMEBondExchangeCalendar 7 | from .calendars.cme_globex_base import CMEGlobexBaseExchangeCalendar 8 | from .calendars.cme_globex_agriculture import ( 9 | CMEGlobexAgricultureExchangeCalendar, 10 | CMEGlobexLivestockExchangeCalendar, 11 | CMEGlobexGrainsAndOilseedsExchangeCalendar, 12 | ) 13 | from .calendars.cme_globex_crypto import CMEGlobexCryptoExchangeCalendar 14 | from .calendars.cme_globex_energy_and_metals import ( 15 | CMEGlobexEnergyAndMetalsExchangeCalendar, 16 | ) 17 | from .calendars.cme_globex_equities import CMEGlobexEquitiesExchangeCalendar 18 | from .calendars.cme_globex_fx import CMEGlobexFXExchangeCalendar 19 | from .calendars.cme_globex_fixed_income import CMEGlobexFixedIncomeCalendar 20 | from .calendars.eurex import EUREXExchangeCalendar 21 | from .calendars.eurex_fixed_income import EUREXFixedIncomeCalendar 22 | from .calendars.hkex import HKEXExchangeCalendar 23 | from .calendars.ice import ICEExchangeCalendar 24 | from .calendars.iex import IEXExchangeCalendar 25 | from .calendars.jpx import JPXExchangeCalendar 26 | from .calendars.lse import LSEExchangeCalendar 27 | from .calendars.nyse import NYSEExchangeCalendar 28 | from .calendars.ose import OSEExchangeCalendar 29 | from .calendars.sifma import ( 30 | SIFMAUSExchangeCalendar, 31 | SIFMAUKExchangeCalendar, 32 | SIFMAJPExchangeCalendar, 33 | ) 34 | from .calendars.six import SIXExchangeCalendar 35 | from .calendars.sse import SSEExchangeCalendar 36 | from .calendars.tase import TASEExchangeCalendar 37 | from .calendars.tsx import TSXExchangeCalendar 38 | from .calendars.mirror import * 39 | 40 | 41 | def get_calendar(name, open_time=None, close_time=None) -> MarketCalendar: 42 | """ 43 | Retrieves an instance of an MarketCalendar whose name is given. 44 | 45 | :param name: The name of the MarketCalendar to be retrieved. 46 | :param open_time: Market open time override as datetime.time object. If None then default is used. 47 | :param close_time: Market close time override as datetime.time object. If None then default is used. 48 | :return: MarketCalendar of the desired calendar. 49 | """ 50 | return MarketCalendar.factory(name, open_time=open_time, close_time=close_time) 51 | 52 | 53 | def get_calendar_names(): 54 | """All Market Calendar names and aliases that can be used in "factory" 55 | :return: list(str) 56 | """ 57 | return MarketCalendar.calendar_names() 58 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsheftel/pandas_market_calendars/0b574d560738665970e532e94ea9584522fc09a0/pandas_market_calendars/calendars/__init__.py -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/asx.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | from pandas.tseries.holiday import AbstractHolidayCalendar, GoodFriday, EasterMonday 4 | from zoneinfo import ZoneInfo 5 | 6 | from pandas_market_calendars.holidays.oz import * 7 | from pandas_market_calendars.market_calendar import MarketCalendar 8 | 9 | AbstractHolidayCalendar.start_date = "2011-01-01" 10 | 11 | 12 | class ASXExchangeCalendar(MarketCalendar): 13 | """ 14 | Open Time: 10:00 AM, Australia/Sydney 15 | Close Time: 4:10 PM, Australia/Sydney 16 | 17 | 18 | Regularly-Observed Holidays: 19 | - New Year's Day (observed on Monday when Jan 1 is a Saturday or Sunday) 20 | - Australia Day (observed on Monday when Jan 26 is a Saturday or Sunday) 21 | - Good Friday (two days before Easter Sunday) 22 | - Easter Monday (the Monday after Easter Sunday) 23 | - ANZAC Day (April 25) 24 | - Queen's Birthday (second Monday in June) 25 | - Christmas Day (December 25, Saturday/Sunday to Monday) 26 | - Boxing Day (December 26, Saturday to Monday, Sunday to Tuesday) 27 | 28 | 29 | Regularly-Observed Early Closes: 30 | - Last Business Day before Christmas Day 31 | - Last Business Day of the Year 32 | 33 | """ 34 | 35 | aliases = ["ASX"] 36 | regular_market_times = { 37 | "market_open": ((None, time(10)),), 38 | "market_close": ((None, time(16, 10)),), 39 | } 40 | 41 | @property 42 | def name(self): 43 | return "ASX" 44 | 45 | @property 46 | def full_name(self): 47 | return "Australian Securities Exchange" 48 | 49 | @property 50 | def tz(self): 51 | return ZoneInfo("Australia/Sydney") 52 | 53 | @property 54 | def regular_holidays(self): 55 | return AbstractHolidayCalendar( 56 | rules=[ 57 | OZNewYearsDay, 58 | AustraliaDay, 59 | AnzacDay, 60 | QueensBirthday, 61 | Christmas, 62 | BoxingDay, 63 | GoodFriday, 64 | EasterMonday, 65 | ] 66 | ) 67 | 68 | @property 69 | def adhoc_holidays(self): 70 | return UniqueCloses 71 | -------------------------------------------------------------------------------- /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 | from datetime import time 17 | 18 | from pandas import Timestamp 19 | from pandas.tseries.holiday import ( 20 | AbstractHolidayCalendar, 21 | Day, 22 | Easter, 23 | GoodFriday, 24 | Holiday, 25 | ) 26 | from zoneinfo import ZoneInfo 27 | 28 | from pandas_market_calendars.market_calendar import FRIDAY, MarketCalendar 29 | 30 | # Universal Confraternization (new years day) 31 | ConfUniversal = Holiday( 32 | "Dia da Confraternizacao Universal", 33 | month=1, 34 | day=1, 35 | ) 36 | # Sao Paulo city birthday 37 | AniversarioSaoPaulo = Holiday( 38 | "Aniversario de Sao Paulo", month=1, day=25, end_date="2021-12-31" 39 | ) 40 | # Carnival Monday 41 | CarnavalSegunda = Holiday("Carnaval Segunda", month=1, day=1, offset=[Easter(), Day(-48)]) 42 | # Carnival Tuesday 43 | CarnavalTerca = Holiday("Carnaval Terca", month=1, day=1, offset=[Easter(), Day(-47)]) 44 | # Ash Wednesday (short day) 45 | QuartaCinzas = Holiday("Quarta Cinzas", month=1, day=1, offset=[Easter(), Day(-46)]) 46 | # Good Friday 47 | SextaPaixao = GoodFriday 48 | # Feast of the Most Holy Body of Christ 49 | CorpusChristi = Holiday("Corpus Christi", month=1, day=1, offset=[Easter(), Day(60)]) 50 | # Tiradentes Memorial 51 | Tiradentes = Holiday( 52 | "Tiradentes", 53 | month=4, 54 | day=21, 55 | ) 56 | # Labor Day 57 | DiaTrabalho = Holiday( 58 | "Dia Trabalho", 59 | month=5, 60 | day=1, 61 | ) 62 | # Constitutionalist Revolution 63 | Constitucionalista = Holiday("Constitucionalista", month=7, day=9, start_date="1997-01-01", end_date="2019-12-31") 64 | # Independence Day 65 | Independencia = Holiday( 66 | "Independencia", 67 | month=9, 68 | day=7, 69 | ) 70 | # Our Lady of Aparecida 71 | Aparecida = Holiday( 72 | "Nossa Senhora de Aparecida", 73 | month=10, 74 | day=12, 75 | ) 76 | # All Souls' Day 77 | Finados = Holiday( 78 | "Dia dos Finados", 79 | month=11, 80 | day=2, 81 | ) 82 | # Proclamation of the Republic 83 | ProclamacaoRepublica = Holiday( 84 | "Proclamacao da Republica", 85 | month=11, 86 | day=15, 87 | ) 88 | # Day of Black Awareness (municipal holiday for the city of São Paulo) 89 | ConscienciaNegra = Holiday( 90 | "Dia da Consciencia Negra", 91 | month=11, 92 | day=20, 93 | start_date="2004-01-01", 94 | end_date="2019-12-31", 95 | ) 96 | # Day of Black Awareness (national holiday) 97 | ConscienciaNegraNacional = Holiday( 98 | "Dia da Consciencia Negra", 99 | month=11, 100 | day=20, 101 | start_date="2023-12-22", 102 | ) 103 | # Christmas Eve 104 | VesperaNatal = Holiday( 105 | "Vespera Natal", 106 | month=12, 107 | day=24, 108 | ) 109 | # Christmas 110 | Natal = Holiday( 111 | "Natal", 112 | month=12, 113 | day=25, 114 | ) 115 | # New Year's Eve 116 | AnoNovo = Holiday( 117 | "Ano Novo", 118 | month=12, 119 | day=31, 120 | ) 121 | # New Year's Eve falls on Saturday 122 | AnoNovoSabado = Holiday( 123 | "Ano Novo Sabado", 124 | month=12, 125 | day=30, 126 | days_of_week=(FRIDAY,), 127 | ) 128 | # New Year's Eve falls on Sunday 129 | AnoNovoDomingo = Holiday( 130 | "Ano Novo Domingo", 131 | month=12, 132 | day=29, 133 | days_of_week=(FRIDAY,), 134 | ) 135 | 136 | ########################## 137 | # Non-recurring holidays 138 | ########################## 139 | 140 | Constitucionalista2021 = Timestamp("2021-07-09", tz="UTC") 141 | ConscienciaNegra2021 = Timestamp("2021-11-20", tz="UTC") 142 | 143 | 144 | class BMFExchangeCalendar(MarketCalendar): 145 | """ 146 | Exchange calendar for BM&F BOVESPA 147 | 148 | Open Time: 10:00 AM, Brazil/Sao Paulo 149 | Close Time: 4:00 PM, Brazil/Sao Paulo 150 | 151 | Regularly-Observed Holidays: 152 | - Universal Confraternization (New year's day, Jan 1) 153 | - Sao Paulo City Anniversary (Jan 25 until 2021) 154 | - Carnaval Monday (48 days before Easter) 155 | - Carnaval Tuesday (47 days before Easter) 156 | - Passion of the Christ (Good Friday, 2 days before Easter) 157 | - Corpus Christi (60 days after Easter) 158 | - Tiradentes (April 21) 159 | - Labor day (May 1) 160 | - Constitutionalist Revolution (July 9 from 1997 until 2021, skipping 2020) 161 | - Independence Day (September 7) 162 | - Our Lady of Aparecida Feast (October 12) 163 | - All Souls' Day (November 2) 164 | - Proclamation of the Republic (November 15) 165 | - Day of Black Awareness, municipal holiday for the city of São Paulo (November 20 from 2004 until 2021, skipping 2020) 166 | - Day of Black Awareness, national holiday (November 20 starting in 2024) 167 | - Christmas (December 24 and 25) 168 | - Friday before New Year's Eve (December 30 or 29 if NYE falls on a Saturday or Sunday, respectively) 169 | - New Year's Eve (December 31) 170 | """ 171 | 172 | aliases = ["BMF", "B3"] 173 | regular_market_times = { 174 | "market_open": ((None, time(10, 1)),), 175 | "market_close": ((None, time(16)),), 176 | } 177 | 178 | @property 179 | def name(self): 180 | return "BMF" 181 | 182 | @property 183 | def tz(self): 184 | return ZoneInfo("America/Sao_Paulo") 185 | 186 | @property 187 | def regular_holidays(self): 188 | return AbstractHolidayCalendar( 189 | rules=[ 190 | ConfUniversal, 191 | AniversarioSaoPaulo, 192 | CarnavalSegunda, 193 | CarnavalTerca, 194 | SextaPaixao, 195 | CorpusChristi, 196 | Tiradentes, 197 | DiaTrabalho, 198 | Constitucionalista, 199 | Independencia, 200 | Aparecida, 201 | Finados, 202 | ProclamacaoRepublica, 203 | ConscienciaNegra, 204 | ConscienciaNegraNacional, 205 | VesperaNatal, 206 | Natal, 207 | AnoNovo, 208 | AnoNovoSabado, 209 | AnoNovoDomingo, 210 | ] 211 | ) 212 | 213 | @property 214 | def adhoc_holidays(self): 215 | return [Constitucionalista2021, ConscienciaNegra2021] 216 | 217 | @property 218 | def special_opens(self): 219 | return [(time(13, 1), AbstractHolidayCalendar(rules=[QuartaCinzas]))] 220 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cboe.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | from itertools import chain 3 | 4 | import pandas as pd 5 | from pandas.tseries.holiday import ( 6 | AbstractHolidayCalendar, 7 | GoodFriday, 8 | USLaborDay, 9 | USPresidentsDay, 10 | USThanksgivingDay, 11 | Holiday, 12 | ) 13 | from zoneinfo import ZoneInfo 14 | 15 | from pandas_market_calendars.holidays.us import ( 16 | Christmas, 17 | USBlackFridayInOrAfter1993, 18 | USIndependenceDay, 19 | USMartinLutherKingJrAfter1998, 20 | USMemorialDay, 21 | USNewYearsDay, 22 | HurricaneSandyClosings, 23 | USNationalDaysofMourning, 24 | USJuneteenthAfter2022, 25 | ) 26 | from pandas_market_calendars.market_calendar import MarketCalendar 27 | 28 | 29 | def good_friday_unless_christmas_nye_friday(dt): 30 | """ 31 | Good Friday is a valid trading day if Christmas Day or New Years Day fall 32 | on a Friday. 33 | """ 34 | if isinstance(dt, pd.DatetimeIndex): 35 | # Pandas < 2.1.0 will call with an index and fall-back to element by element 36 | # Pandas == 2.1.0 will only call element by element 37 | raise NotImplementedError() 38 | 39 | year = dt.year 40 | christmas_weekday = Christmas.observance( 41 | pd.Timestamp(year=year, month=12, day=25) 42 | ).weekday() 43 | nyd_weekday = USNewYearsDay.observance( 44 | pd.Timestamp(year=year, month=1, day=1) 45 | ).weekday() 46 | if christmas_weekday != 4 and nyd_weekday != 4: 47 | return GoodFriday.dates( 48 | pd.Timestamp(year=year, month=1, day=1), 49 | pd.Timestamp(year=year, month=12, day=31), 50 | )[0] 51 | else: 52 | # Not a holiday so use NaT to ensure it gets removed 53 | return pd.NaT 54 | 55 | 56 | GoodFridayUnlessChristmasNYEFriday = Holiday( 57 | name="Good Friday CFE", 58 | month=1, 59 | day=1, 60 | observance=good_friday_unless_christmas_nye_friday, 61 | ) 62 | 63 | 64 | class CFEExchangeCalendar(MarketCalendar): 65 | """ 66 | Exchange calendar for the CBOE Futures Exchange (CFE). 67 | 68 | http://cfe.cboe.com/aboutcfe/expirationcalendar.aspx 69 | 70 | Open Time: 8:30am, America/Chicago 71 | Close Time: 3:15pm, America/Chicago 72 | 73 | (We are ignoring extended trading hours for now) 74 | """ 75 | 76 | aliases = ["CFE", "CBOE_Futures"] 77 | regular_market_times = { 78 | "market_open": ((None, time(8, 30)),), 79 | "market_close": ((None, time(15, 15)),), 80 | } 81 | 82 | @property 83 | def name(self): 84 | return "CFE" 85 | 86 | @property 87 | def full_name(self): 88 | return "CBOE Futures Exchange" 89 | 90 | @property 91 | def tz(self): 92 | return ZoneInfo("America/Chicago") 93 | 94 | @property 95 | def regular_holidays(self): 96 | return AbstractHolidayCalendar( 97 | rules=[ 98 | USNewYearsDay, 99 | USMartinLutherKingJrAfter1998, 100 | USPresidentsDay, 101 | GoodFridayUnlessChristmasNYEFriday, 102 | USJuneteenthAfter2022, 103 | USIndependenceDay, 104 | USMemorialDay, 105 | USLaborDay, 106 | USThanksgivingDay, 107 | Christmas, 108 | ] 109 | ) 110 | 111 | @property 112 | def special_closes(self): 113 | return [ 114 | ( 115 | time(12, 15), 116 | AbstractHolidayCalendar( 117 | rules=[ 118 | USBlackFridayInOrAfter1993, 119 | ] 120 | ), 121 | ) 122 | ] 123 | 124 | @property 125 | def adhoc_holidays(self): 126 | return list( 127 | chain( 128 | HurricaneSandyClosings, 129 | USNationalDaysofMourning, 130 | ) 131 | ) 132 | 133 | 134 | class CBOEEquityOptionsExchangeCalendar(CFEExchangeCalendar): 135 | name = "CBOE_Equity_Options" 136 | aliases = [name] 137 | regular_market_times = { 138 | "market_open": ((None, time(8, 30)),), 139 | "market_close": ((None, time(15)),), 140 | } 141 | 142 | 143 | class CBOEIndexOptionsExchangeCalendar(CFEExchangeCalendar): 144 | name = "CBOE_Index_Options" 145 | aliases = [name] 146 | regular_market_times = { 147 | "market_open": ((None, time(8, 30)),), 148 | "market_close": ((None, time(15, 15)),), 149 | } 150 | -------------------------------------------------------------------------------- /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/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 | from abc import ABC, abstractmethod 17 | 18 | from pandas.tseries.holiday import ( 19 | AbstractHolidayCalendar, 20 | GoodFriday, 21 | USLaborDay, 22 | USPresidentsDay, 23 | USThanksgivingDay, 24 | ) 25 | from zoneinfo import ZoneInfo 26 | 27 | from pandas_market_calendars.holidays.us import ( 28 | Christmas, 29 | ChristmasEveBefore1993, 30 | ChristmasEveInOrAfter1993, 31 | USBlackFridayInOrAfter1993, 32 | USIndependenceDay, 33 | USMartinLutherKingJrAfter1998, 34 | USMemorialDay, 35 | USJuneteenthAfter2022, 36 | USNewYearsDay, 37 | ) 38 | from pandas_market_calendars.market_calendar import MarketCalendar 39 | 40 | 41 | class CMEGlobexBaseExchangeCalendar(MarketCalendar, ABC): 42 | """ 43 | Base Exchange Calendar for CME. 44 | 45 | CME Markets: https://www.cmegroup.com/markets/agriculture.html#overview 46 | - Agriculture 47 | - Crypto 48 | - Energy 49 | - Equity Index 50 | - FX 51 | - Interest Rates 52 | - Metals 53 | - Options 54 | 55 | Holiays for which entire GLOBEX is closed: 56 | - New Years Day 57 | - Good Friday 58 | - Christmas 59 | 60 | Product Specific Closures: 61 | - MLK Day 62 | - Presidents Day 63 | - Memorial Day 64 | - Juneteenth 65 | - US Independence Day 66 | - US Labor Day 67 | - US Thanksgiving Day 68 | """ 69 | 70 | @property 71 | @abstractmethod 72 | def name(self): 73 | """ 74 | Name of the market 75 | 76 | :return: string name 77 | """ 78 | raise NotImplementedError() 79 | 80 | @property 81 | def tz(self): 82 | return ZoneInfo("America/Chicago") 83 | 84 | @property 85 | def regular_holidays(self): 86 | return AbstractHolidayCalendar( 87 | rules=[ 88 | USNewYearsDay, 89 | GoodFriday, 90 | Christmas, 91 | ] 92 | ) 93 | 94 | # I can't find any reference to these special closings onther than NYSE 95 | # @property 96 | # def adhoc_holidays(self): 97 | # return USNationalDaysofMourning 98 | 99 | @property 100 | def special_closes(self): 101 | return [ 102 | ( 103 | self.special_close_time, 104 | AbstractHolidayCalendar( 105 | rules=[ 106 | USMartinLutherKingJrAfter1998, 107 | USPresidentsDay, 108 | USMemorialDay, 109 | USJuneteenthAfter2022, 110 | USLaborDay, 111 | USIndependenceDay, 112 | USThanksgivingDay, 113 | USBlackFridayInOrAfter1993, 114 | ChristmasEveBefore1993, 115 | ChristmasEveInOrAfter1993, 116 | ] 117 | ), 118 | ) 119 | ] 120 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_crypto.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | 3 | from zoneinfo import ZoneInfo 4 | from pandas.tseries.holiday import AbstractHolidayCalendar 5 | 6 | from pandas_market_calendars.holidays.cme import ( 7 | GoodFriday2021, 8 | GoodFriday2022, 9 | GoodFridayAfter2022, 10 | GoodFridayBefore2021, 11 | USIndependenceDayBefore2022PreviousDay, 12 | ) 13 | from pandas_market_calendars.holidays.cme_globex import ( 14 | ChristmasCME, 15 | USMartinLutherKingJrFrom2022, 16 | USMartinLutherKingJrPre2022, 17 | USPresidentsDayFrom2022, 18 | USPresidentsDayPre2022, 19 | USMemorialDayFrom2022, 20 | USMemorialDayPre2022, 21 | USJuneteenthFrom2022, 22 | USIndependenceDayFrom2022, 23 | USIndependenceDayPre2022, 24 | USLaborDayFrom2022, 25 | USLaborDayPre2022, 26 | USThanksgivingDayFrom2022, 27 | USThanksgivingDayPre2022, 28 | USThanksgivingFridayFrom2021, 29 | USThanksgivingFridayPre2021, 30 | ) 31 | from pandas_market_calendars.holidays.us import ( 32 | ChristmasEveInOrAfter1993, 33 | USNewYearsDay, 34 | ) 35 | from .cme_globex_base import CMEGlobexBaseExchangeCalendar 36 | 37 | 38 | # https://github.com/rsheftel/pandas_market_calendars/blob/master/docs/new_market.rst 39 | class CMEGlobexCryptoExchangeCalendar(CMEGlobexBaseExchangeCalendar): 40 | # The label you fetch the exchange with in mcal.get_calendar('CME Globex ...') 41 | aliases = ["CME Globex Cryptocurrencies", "CME Globex Crypto"] 42 | 43 | # https://www.cmegroup.com/markets/cryptocurrencies/bitcoin/bitcoin.contractSpecs.html 44 | regular_market_times = { 45 | # Tuple[Tuple[first date used, time, offset], ...] 46 | # -1 offset indicates that the open is on the previous day 47 | # None for first date used marks the start, subsequent market times must have an actual timestamp 48 | "market_open": ((None, dt.time(17, tzinfo=ZoneInfo("America/Chicago")), -1),), 49 | "market_close": ( 50 | ( 51 | None, 52 | dt.time(16, tzinfo=ZoneInfo("America/Chicago")), 53 | ), 54 | ), 55 | "break_start": ( 56 | ( 57 | None, 58 | dt.time(16, tzinfo=ZoneInfo("America/Chicago")), 59 | ), 60 | ), 61 | "break_end": ( 62 | ( 63 | None, 64 | dt.time(17, tzinfo=ZoneInfo("America/Chicago")), 65 | ), 66 | ), 67 | } 68 | 69 | @property 70 | def tz(self): 71 | # Central Time 72 | return ZoneInfo("America/Chicago") 73 | 74 | @property 75 | def name(self): 76 | return "CME Globex Crypto" 77 | 78 | # Check the .zip files at the bottom of this page 79 | # https://www.cmegroup.com/tools-information/holiday-calendar.html?redirect=/tools-information/holiday-calendar/#cmeGlobex 80 | # Note: many of the holiday objects (ie. GoodFridayBefore2021) were originally made for equities and other markets 81 | # and hence have a start_date starting before crypto is actually available 82 | 83 | @property 84 | def regular_holidays(self): 85 | # Days where the market is fully closed 86 | return AbstractHolidayCalendar( 87 | rules=[ 88 | GoodFridayBefore2021, 89 | GoodFriday2022, 90 | ChristmasCME, 91 | USNewYearsDay, 92 | ] 93 | ) 94 | 95 | @property 96 | def special_closes(self): 97 | # Days where the market closes early 98 | # list[Tuple[time, AbstractHolidayCalendar]] 99 | return [ 100 | ( 101 | dt.time(8, 15, tzinfo=ZoneInfo("America/Chicago")), 102 | AbstractHolidayCalendar( 103 | rules=[ 104 | GoodFriday2021, 105 | ] 106 | ), 107 | ), 108 | ( 109 | dt.time(10, 15, tzinfo=ZoneInfo("America/Chicago")), 110 | AbstractHolidayCalendar( 111 | rules=[ 112 | GoodFridayAfter2022, 113 | ] 114 | ), 115 | ), 116 | ( 117 | dt.time(12, tzinfo=ZoneInfo("America/Chicago")), 118 | AbstractHolidayCalendar( 119 | rules=[ 120 | USMartinLutherKingJrPre2022, 121 | USPresidentsDayPre2022, 122 | USMemorialDayPre2022, 123 | USIndependenceDayPre2022, 124 | USLaborDayPre2022, 125 | USThanksgivingDayPre2022, 126 | ] 127 | ), 128 | ), 129 | ( 130 | dt.time(12, 15, tzinfo=ZoneInfo("America/Chicago")), 131 | AbstractHolidayCalendar( 132 | rules=[ 133 | ChristmasEveInOrAfter1993, 134 | USIndependenceDayBefore2022PreviousDay, 135 | USThanksgivingFridayPre2021, 136 | ] 137 | ), 138 | ), 139 | ( 140 | dt.time(12, 45, tzinfo=ZoneInfo("America/Chicago")), 141 | AbstractHolidayCalendar(rules=[USThanksgivingFridayFrom2021]), 142 | ), 143 | # TODO: this market already closes at 1600 normally, do we need these holidays? 144 | ( 145 | dt.time(16, tzinfo=ZoneInfo("America/Chicago")), 146 | AbstractHolidayCalendar( 147 | rules=[ 148 | USMartinLutherKingJrFrom2022, 149 | USPresidentsDayFrom2022, 150 | USMemorialDayFrom2022, 151 | USJuneteenthFrom2022, 152 | USIndependenceDayFrom2022, 153 | USLaborDayFrom2022, 154 | USThanksgivingDayFrom2022, 155 | ] 156 | ), 157 | ), 158 | ] 159 | -------------------------------------------------------------------------------- /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 | from datetime import time 17 | 18 | from pandas.tseries.holiday import ( 19 | AbstractHolidayCalendar, 20 | ) # , GoodFriday, USLaborDay, USPresidentsDay, USThanksgivingDay 21 | from zoneinfo import ZoneInfo 22 | 23 | from pandas_market_calendars.holidays.cme_globex import ( 24 | USMartinLutherKingJrFrom2022, 25 | USMartinLutherKingJrPre2022, 26 | USNewYearsDay, 27 | USPresidentsDayFrom2022, 28 | USPresidentsDayPre2022, 29 | GoodFriday, 30 | USMemorialDayFrom2022, 31 | USMemorialDayPre2022, 32 | USJuneteenthFrom2022, 33 | USIndependenceDayFrom2022, 34 | USIndependenceDayPre2022, 35 | USLaborDay, 36 | USThanksgivingDayFrom2022, 37 | USThanksgivingDayPre2022, 38 | FridayAfterThanksgiving, 39 | ChristmasCME, 40 | ) 41 | from .cme_globex_base import CMEGlobexBaseExchangeCalendar 42 | 43 | 44 | # from .holidays_us import (Christmas, ChristmasEveBefore1993, ChristmasEveInOrAfter1993, USBlackFridayInOrAfter1993, 45 | # USIndependenceDay, USMartinLutherKingJrAfter1998, USMemorialDay, USJuneteenthAfter2022, 46 | # USNationalDaysofMourning, USNewYearsDay) 47 | 48 | 49 | class CMEGlobexEnergyAndMetalsExchangeCalendar(CMEGlobexBaseExchangeCalendar): 50 | """ 51 | Exchange calendar for CME for Energy and Metals products 52 | 53 | Both follow the same trading/holiday schedule 54 | 55 | NOT IMPLEMENTED: Dubai Mercantile Exchange (DME) follows this schedule but with holiday exceptions. 56 | 57 | Energy Products: 58 | Crude and Refined: https://www.cmegroup.com/trading/energy/crude-and-refined-products.html 59 | - HO NY Harbor ULSD Futures 60 | - CL Crude Oil Futures 61 | - RB RBOB Gasoline Futures 62 | - MCL Micro WTI Crude Oil Futures 63 | Natural Gas 64 | - NG Henry Hub Natural Gas Futures 65 | - TTF Dutch TTF Natural Gas Calendar Month Futures 66 | - NN Henry Hub Natural Gas Last Day Financial Futures 67 | Voluntary Carbon Emissions Offset Futures 68 | - CGO CBL Core Global Emmissions Offset (C-GEO) Futures 69 | - NGO CBL Nature-based Global Emissionns Offset Futures 70 | - GEO CBL Global Emissions Offset Futures 71 | 72 | Metals Products: https://www.cmegroup.com/markets/metals.html 73 | Precious Metals 74 | - GC Gold Futures 75 | - SI Silver Futures 76 | - PL Platinum Futures 77 | Base Metals 78 | - HG Copper Futures 79 | - ALI Aluminum Futures 80 | - QC E-mini Copper Futures 81 | Ferrous Metals 82 | - HRC U.S. Midwest Domestic Hot-Rolled Coil Steel (CRU) Index Futures 83 | - BUS U.S. Midwest Busheling Ferrous Scrap (AMM) Futures 84 | - TIO Iron Ore 62% Fe, CFR China (Platts) Futures 85 | 86 | Sample GLOBEX Trading Times 87 | https://www.cmegroup.com/markets/energy/crude-oil/light-sweet-crude.contractSpecs.html 88 | Sunday - Friday: 5:00pm - 4:00 pm CT 89 | 90 | Calendar: http://www.cmegroup.com/tools-information/holiday-calendar.html 91 | """ 92 | 93 | aliases = [ 94 | "CMEGlobex_EnergyAndMetals", 95 | "CMEGlobex_Energy", 96 | "CMEGlobex_CrudeAndRefined", 97 | "CMEGlobex_NYHarbor", 98 | "CMEGlobex_HO", 99 | "HO", 100 | "CMEGlobex_Crude", 101 | "CMEGlobex_CL", 102 | "CL", 103 | "CMEGlobex_Gas", 104 | "CMEGlobex_RB", 105 | "RB", 106 | "CMEGlobex_MicroCrude", 107 | "CMEGlobex_MCL", 108 | "MCL", 109 | "CMEGlobex_NatGas", 110 | "CMEGlobex_NG", 111 | "NG", 112 | "CMEGlobex_Dutch_NatGas", 113 | "CMEGlobex_TTF", 114 | "TTF", 115 | "CMEGlobex_LastDay_NatGas", 116 | "CMEGlobex_NN", 117 | "NN", 118 | "CMEGlobex_CarbonOffset", 119 | "CMEGlobex_CGO", 120 | "CGO", 121 | "C-GEO", 122 | "CMEGlobex_NGO", 123 | "NGO", 124 | "CMEGlobex_GEO", 125 | "GEO", 126 | "CMEGlobex_Metals", 127 | "CMEGlobex_PreciousMetals", 128 | "CMEGlobex_Gold", 129 | "CMEGlobex_GC", 130 | "GC", 131 | "CMEGlobex_Silver", 132 | "CMEGlobex_SI", 133 | "SI", 134 | "CMEGlobex_Platinum", 135 | "CMEGlobex_PL", 136 | "PL", 137 | "CMEGlobex_BaseMetals", 138 | "CMEGlobex_Copper", 139 | "CMEGlobex_HG", 140 | "HG", 141 | "CMEGlobex_Aluminum", 142 | "CMEGlobex_ALI", 143 | "ALI", 144 | "CMEGlobex_Copper", 145 | "CMEGlobex_QC", 146 | "QC", 147 | "CMEGlobex_FerrousMetals", 148 | "CMEGlobex_HRC", 149 | "HRC", 150 | "CMEGlobex_BUS", 151 | "BUS", 152 | "CMEGlobex_TIO", 153 | "TIO", 154 | ] 155 | 156 | regular_market_times = { 157 | "market_open": ((None, time(17), -1),), # Sunday offset. Central Timezone (CT) 158 | "market_close": ((None, time(16)),), 159 | } 160 | 161 | @property 162 | def name(self): 163 | return "CMEGlobex_EnergyAndMetals" 164 | 165 | @property 166 | def regular_holidays(self): 167 | return AbstractHolidayCalendar( 168 | rules=[ 169 | USNewYearsDay, 170 | GoodFriday, 171 | ChristmasCME, 172 | ] 173 | ) 174 | 175 | # @property 176 | # def adhoc_holidays(self): 177 | # return USNationalDaysofMourning 178 | 179 | @property 180 | def special_closes(self): 181 | return [ 182 | ( 183 | time(12, tzinfo=ZoneInfo("America/Chicago")), 184 | AbstractHolidayCalendar( 185 | rules=[ 186 | USMartinLutherKingJrPre2022, 187 | USPresidentsDayPre2022, 188 | USMemorialDayPre2022, 189 | USIndependenceDayPre2022, 190 | USLaborDay, 191 | USThanksgivingDayPre2022, 192 | ] 193 | ), 194 | ), 195 | ( 196 | time(12, 45, tzinfo=ZoneInfo("America/Chicago")), 197 | AbstractHolidayCalendar( 198 | rules=[ 199 | FridayAfterThanksgiving, 200 | ] 201 | ), 202 | ), 203 | ( 204 | time(13, 30, tzinfo=ZoneInfo("America/Chicago")), 205 | AbstractHolidayCalendar( 206 | rules=[ 207 | USMartinLutherKingJrFrom2022, 208 | USPresidentsDayFrom2022, 209 | USMemorialDayFrom2022, 210 | USJuneteenthFrom2022, 211 | USIndependenceDayFrom2022, 212 | USThanksgivingDayFrom2022, 213 | ] 214 | ), 215 | ), 216 | ] 217 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/cme_globex_equities.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | from pandas.tseries.holiday import AbstractHolidayCalendar 4 | from zoneinfo import ZoneInfo 5 | 6 | from pandas_market_calendars.holidays.cme import ( 7 | USMartinLutherKingJrAfter1998Before2015, 8 | USMartinLutherKingJrAfter2015, 9 | USPresidentsDayBefore2015, 10 | USPresidentsDayAfter2015, 11 | GoodFridayBefore2021NotEarlyClose, 12 | GoodFriday2010, 13 | GoodFriday2012, 14 | GoodFriday2015, 15 | GoodFriday2021, 16 | GoodFriday2022, 17 | GoodFridayAfter2022, 18 | USMemorialDay2013AndPrior, 19 | USMemorialDayAfter2013, 20 | USIndependenceDayBefore2022PreviousDay, 21 | USIndependenceDayBefore2014, 22 | USIndependenceDayAfter2014, 23 | USLaborDayStarting1887Before2014, 24 | USLaborDayStarting1887After2014, 25 | USThanksgivingBefore2014, 26 | USThanksgivingAfter2014, 27 | USThanksgivingFriday, 28 | ) 29 | from pandas_market_calendars.holidays.us import ( 30 | USNewYearsDay, 31 | ChristmasEveInOrAfter1993, 32 | Christmas, 33 | USJuneteenthAfter2022, 34 | ) 35 | from .cme_globex_base import CMEGlobexBaseExchangeCalendar 36 | 37 | 38 | class CMEGlobexEquitiesExchangeCalendar(CMEGlobexBaseExchangeCalendar): 39 | aliases = ["CME Globex Equity"] 40 | 41 | regular_market_times = { 42 | "market_open": ((None, time(17), -1),), # offset by -1 day 43 | "market_close": ((None, time(16)),), 44 | } 45 | 46 | @property 47 | def tz(self): 48 | return ZoneInfo("America/Chicago") 49 | 50 | @property 51 | def name(self): 52 | """ 53 | Name of the market 54 | 55 | :return: string name 56 | """ 57 | return "CME Globex Equities" 58 | 59 | @property 60 | def regular_holidays(self): 61 | return AbstractHolidayCalendar( 62 | rules=[ 63 | USNewYearsDay, 64 | GoodFridayBefore2021NotEarlyClose, 65 | GoodFriday2022, 66 | Christmas, 67 | ] 68 | ) 69 | 70 | @property 71 | def special_closes(self): 72 | # Source https://www.cmegroup.com/tools-information/holiday-calendar.html 73 | return [ 74 | ( 75 | time(10, 30), 76 | AbstractHolidayCalendar( 77 | rules=[ 78 | USMartinLutherKingJrAfter1998Before2015, 79 | USPresidentsDayBefore2015, 80 | USMemorialDay2013AndPrior, 81 | USIndependenceDayBefore2014, 82 | USLaborDayStarting1887Before2014, 83 | USThanksgivingBefore2014, 84 | ] 85 | ), 86 | ), 87 | ( 88 | time(12, 15), 89 | AbstractHolidayCalendar( 90 | rules=[ 91 | USIndependenceDayBefore2022PreviousDay, 92 | USThanksgivingFriday, 93 | ChristmasEveInOrAfter1993, 94 | ] 95 | ), 96 | ), 97 | ( 98 | time(12), 99 | AbstractHolidayCalendar( 100 | rules=[ 101 | USMartinLutherKingJrAfter2015, 102 | USPresidentsDayAfter2015, 103 | USMemorialDayAfter2013, 104 | USIndependenceDayAfter2014, 105 | USLaborDayStarting1887After2014, 106 | USThanksgivingAfter2014, 107 | USJuneteenthAfter2022, 108 | ] 109 | ), 110 | ), 111 | ( 112 | time(8, 15), 113 | AbstractHolidayCalendar( 114 | rules=[ 115 | GoodFriday2010, 116 | GoodFriday2012, 117 | GoodFriday2015, 118 | GoodFriday2021, 119 | GoodFridayAfter2022, 120 | ] 121 | ), 122 | ), 123 | ] 124 | -------------------------------------------------------------------------------- /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/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( 98 | rules=[USThanksgivingFriday, ChristmasEveInOrAfter1993] 99 | ), 100 | ), 101 | ] 102 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/eurex.py: -------------------------------------------------------------------------------- 1 | # 2 | # kewlfft 3 | # 4 | 5 | from datetime import time 6 | 7 | from pandas.tseries.holiday import ( 8 | AbstractHolidayCalendar, 9 | EasterMonday, 10 | GoodFriday, 11 | Holiday, 12 | previous_friday, 13 | ) 14 | from zoneinfo import ZoneInfo 15 | 16 | from pandas_market_calendars.market_calendar import ( 17 | FRIDAY, 18 | MONDAY, 19 | MarketCalendar, 20 | THURSDAY, 21 | TUESDAY, 22 | WEDNESDAY, 23 | ) 24 | 25 | # New Year's Eve 26 | EUREXNewYearsEve = Holiday( 27 | "New Year's Eve", 28 | month=12, 29 | day=31, 30 | observance=previous_friday, 31 | ) 32 | # New Year's Day 33 | EUREXNewYearsDay = Holiday( 34 | "New Year's Day", 35 | month=1, 36 | day=1, 37 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 38 | ) 39 | # Early May bank holiday 40 | MayBank = Holiday( 41 | "Early May Bank Holiday", 42 | month=5, 43 | day=1, 44 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 45 | ) 46 | # Christmas Eve 47 | ChristmasEve = Holiday( 48 | "Christmas Eve", 49 | month=12, 50 | day=24, 51 | observance=previous_friday, 52 | ) 53 | # Christmas 54 | Christmas = Holiday( 55 | "Christmas", 56 | month=12, 57 | day=25, 58 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 59 | ) 60 | # If christmas day is Saturday Monday 27th is a holiday 61 | # If christmas day is sunday the Tuesday 27th is a holiday 62 | WeekendChristmas = Holiday( 63 | "Weekend Christmas", 64 | month=12, 65 | day=27, 66 | days_of_week=(MONDAY, TUESDAY), 67 | ) 68 | # Boxing day 69 | BoxingDay = Holiday( 70 | "Boxing Day", 71 | month=12, 72 | day=26, 73 | ) 74 | # If boxing day is saturday then Monday 28th is a holiday 75 | # If boxing day is sunday then Tuesday 28th is a holiday 76 | WeekendBoxingDay = Holiday( 77 | "Weekend Boxing Day", 78 | month=12, 79 | day=28, 80 | days_of_week=(MONDAY, TUESDAY), 81 | ) 82 | 83 | 84 | class EUREXExchangeCalendar(MarketCalendar): 85 | """ 86 | Exchange calendar for EUREX 87 | 88 | """ 89 | 90 | aliases = ["EUREX"] 91 | regular_market_times = { 92 | "market_open": ((None, time(9)),), 93 | "market_close": ((None, time(17, 30)),), 94 | } 95 | 96 | @property 97 | def name(self): 98 | return "EUREX" 99 | 100 | @property 101 | def tz(self): 102 | return ZoneInfo("Europe/Berlin") 103 | 104 | @property 105 | def regular_holidays(self): 106 | return AbstractHolidayCalendar( 107 | rules=[ 108 | EUREXNewYearsDay, 109 | GoodFriday, 110 | EasterMonday, 111 | MayBank, 112 | Christmas, 113 | WeekendChristmas, 114 | BoxingDay, 115 | WeekendBoxingDay, 116 | ] 117 | ) 118 | 119 | @property 120 | def special_closes(self): 121 | return [ 122 | ( 123 | time(12, 30), 124 | AbstractHolidayCalendar( 125 | rules=[ 126 | ChristmasEve, 127 | EUREXNewYearsEve, 128 | ] 129 | ), 130 | ) 131 | ] 132 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/eurex_fixed_income.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | from pandas.tseries.holiday import ( 4 | AbstractHolidayCalendar, 5 | EasterMonday, 6 | GoodFriday, 7 | Holiday, 8 | ) 9 | from zoneinfo import ZoneInfo 10 | 11 | from pandas_market_calendars.market_calendar import ( 12 | FRIDAY, 13 | MONDAY, 14 | MarketCalendar, 15 | THURSDAY, 16 | TUESDAY, 17 | WEDNESDAY, 18 | ) 19 | 20 | # New Year's Day 21 | EUREXNewYearsDay = Holiday( 22 | "New Year's Day", 23 | month=1, 24 | day=1, 25 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 26 | ) 27 | # Early May bank holiday 28 | MayBank = Holiday( 29 | "Early May Bank Holiday", 30 | month=5, 31 | day=1, 32 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 33 | ) 34 | # Christmas Eve 35 | ChristmasEve = Holiday( 36 | "Christmas Eve", 37 | month=12, 38 | day=24, 39 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 40 | ) 41 | # Christmas 42 | Christmas = Holiday( 43 | "Christmas", 44 | month=12, 45 | day=25, 46 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 47 | ) 48 | # Boxing day 49 | BoxingDay = Holiday( 50 | "Boxing Day", 51 | month=12, 52 | day=26, 53 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 54 | ) 55 | # New Year's Eve 56 | NewYearsEve = Holiday( 57 | "New Year's Eve", 58 | month=12, 59 | day=31, 60 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 61 | ) 62 | 63 | 64 | class EUREXFixedIncomeCalendar(MarketCalendar): 65 | """ 66 | Trading calendar available here: 67 | https://www.eurex.com/resource/blob/3378814/910cf372738890f691bc1bfbccfd3aef/data/tradingcalendar_2023_en.pdf 68 | """ 69 | 70 | aliases = ["EUREX_Bond"] 71 | 72 | regular_market_times = { 73 | "market_open": ((None, time(1, 10)), ("2018-12-10", time(8, 0))), 74 | "market_close": ((None, time(22)),), 75 | } 76 | 77 | @property 78 | def name(self): 79 | return "EUREX_Bond" 80 | 81 | @property 82 | def tz(self): 83 | return ZoneInfo("Europe/Berlin") 84 | 85 | @property 86 | def regular_holidays(self): 87 | return AbstractHolidayCalendar( 88 | rules=[ 89 | EUREXNewYearsDay, 90 | GoodFriday, 91 | EasterMonday, 92 | MayBank, 93 | ChristmasEve, 94 | Christmas, 95 | BoxingDay, 96 | NewYearsEve, 97 | ] 98 | ) 99 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/ice.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | from itertools import chain 3 | 4 | from pandas import Timestamp 5 | from pandas.tseries.holiday import ( 6 | AbstractHolidayCalendar, 7 | GoodFriday, 8 | USLaborDay, 9 | USPresidentsDay, 10 | USThanksgivingDay, 11 | ) 12 | from zoneinfo import ZoneInfo 13 | 14 | from pandas_market_calendars.holidays.us import ( 15 | Christmas, 16 | USIndependenceDay, 17 | USMartinLutherKingJrAfter1998, 18 | USMemorialDay, 19 | USNationalDaysofMourning, 20 | USNewYearsDay, 21 | ) 22 | from pandas_market_calendars.market_calendar import MarketCalendar 23 | 24 | 25 | class ICEExchangeCalendar(MarketCalendar): 26 | """ 27 | Exchange calendar for ICE US. 28 | 29 | Open Time: 8pm, US/Eastern 30 | Close Time: 6pm, US/Eastern 31 | 32 | https://www.theice.com/publicdocs/futures_us/ICE_Futures_US_Regular_Trading_Hours.pdf # noqa 33 | """ 34 | 35 | aliases = ["ICE", "ICEUS", "NYFE"] 36 | regular_market_times = { 37 | "market_open": ((None, time(20, 1), -1),), # offset by -1 day 38 | "market_close": ((None, time(18)),), 39 | } 40 | 41 | @property 42 | def name(self): 43 | return "ICE" 44 | 45 | @property 46 | def tz(self): 47 | return ZoneInfo("US/Eastern") 48 | 49 | @property 50 | def special_closes(self): 51 | return [ 52 | ( 53 | time(13), 54 | AbstractHolidayCalendar( 55 | rules=[ 56 | USMartinLutherKingJrAfter1998, 57 | USPresidentsDay, 58 | USMemorialDay, 59 | USIndependenceDay, 60 | USLaborDay, 61 | USThanksgivingDay, 62 | ] 63 | ), 64 | ) 65 | ] 66 | 67 | @property 68 | def adhoc_holidays(self): 69 | return list( 70 | chain( 71 | USNationalDaysofMourning, 72 | # ICE was only closed on the first day of the Hurricane Sandy 73 | # closings (was not closed on 2012-10-30) 74 | [Timestamp("2012-10-29", tz="UTC")], 75 | ) 76 | ) 77 | 78 | @property 79 | def regular_holidays(self): 80 | # https://www.theice.com/publicdocs/futures_us/exchange_notices/NewExNot2016Holidays.pdf # noqa 81 | return AbstractHolidayCalendar(rules=[USNewYearsDay, GoodFriday, Christmas]) 82 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/iex.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | from itertools import chain 3 | 4 | from pandas import Timestamp, DatetimeIndex, Timedelta 5 | from pandas.tseries.holiday import AbstractHolidayCalendar 6 | from zoneinfo import ZoneInfo 7 | 8 | from typing import Literal, Union 9 | from pandas_market_calendars import calendar_utils as u 10 | 11 | from pandas_market_calendars.holidays.nyse import ( 12 | USPresidentsDay, 13 | GoodFriday, 14 | USMemorialDay, 15 | USJuneteenthAfter2022, 16 | USIndependenceDay, 17 | USThanksgivingDay, 18 | ChristmasNYSE, 19 | USMartinLutherKingJrAfter1998, 20 | # Ad-Hoc 21 | DayAfterThanksgiving1pmEarlyCloseInOrAfter1993, 22 | DaysBeforeIndependenceDay1pmEarlyCloseAdhoc, 23 | ChristmasEvesAdhoc, 24 | ) 25 | from .nyse import NYSEExchangeCalendar 26 | 27 | 28 | class IEXExchangeCalendar(NYSEExchangeCalendar): 29 | """ 30 | Exchange calendar for the Investor's Exchange (IEX). 31 | 32 | IEX Exchange is a U.S. stock exchange focused on driving performance 33 | for broker-dealers and investors through innovative design and technology. 34 | 35 | Most of this class inherits from NYSEExchangeCalendar since 36 | the holidays are the same. The only variation is (1) IEX began 37 | operation in 2013, and (2) IEX has different hours of operation 38 | 39 | References: 40 | - https://exchange.iex.io/ 41 | - https://iexexchange.io/resources/trading/trading-hours-holidays/index.html 42 | """ 43 | 44 | regular_market_times = { 45 | "pre": (("2013-03-25", time(8)),), 46 | "market_open": ((None, time(9, 30)),), 47 | "market_close": ((None, time(16)),), 48 | "post": ((None, time(17)),), 49 | } 50 | 51 | aliases = ["IEX", "Investors_Exchange"] 52 | 53 | @property 54 | def name(self): 55 | return "IEX" 56 | 57 | @property 58 | def full_name(self): 59 | return "Investor's Exchange" 60 | 61 | @property 62 | def weekmask(self): 63 | return "Mon Tue Wed Thu Fri" 64 | 65 | @property 66 | def regular_holidays(self): 67 | return AbstractHolidayCalendar( 68 | rules=[ 69 | USPresidentsDay, 70 | GoodFriday, 71 | USMemorialDay, 72 | USJuneteenthAfter2022, 73 | USIndependenceDay, 74 | USThanksgivingDay, 75 | ChristmasNYSE, 76 | USMartinLutherKingJrAfter1998, 77 | ] 78 | ) 79 | 80 | @property 81 | def adhoc_holidays(self): 82 | return list( 83 | chain( 84 | ChristmasEvesAdhoc, 85 | ) 86 | ) 87 | 88 | @property 89 | def special_closes(self): 90 | return [ 91 | ( 92 | time(hour=13, tzinfo=ZoneInfo("America/New_York")), 93 | AbstractHolidayCalendar( 94 | rules=[ 95 | DayAfterThanksgiving1pmEarlyCloseInOrAfter1993, 96 | ] 97 | ), 98 | ) 99 | ] 100 | 101 | """Override NYSE calendar special cases""" 102 | 103 | @property 104 | def special_closes_adhoc(self): 105 | return [ 106 | ( 107 | time(13, tzinfo=ZoneInfo("America/New_York")), 108 | DaysBeforeIndependenceDay1pmEarlyCloseAdhoc, 109 | ) 110 | ] 111 | 112 | @property 113 | def special_opens(self): 114 | return [] 115 | 116 | def valid_days(self, start_date, end_date, tz="UTC"): 117 | start_date = Timestamp(start_date) 118 | if start_date.tz is not None: 119 | # Ensure valid Comparison to "2013-08-25" is possible 120 | start_date.tz_convert(self.tz).tz_localize(None) 121 | 122 | # Limit Start_date to the Exchange's Open 123 | start_date = max(start_date, Timestamp("2013-08-25")) 124 | return super().valid_days(start_date, end_date, tz=tz) 125 | 126 | def date_range_htf( 127 | self, 128 | frequency: Union[str, Timedelta, int, float], 129 | start: Union[str, Timestamp, int, float, None] = None, 130 | end: Union[str, Timestamp, int, float, None] = None, 131 | periods: Union[int, None] = None, 132 | closed: Union[Literal["left", "right"], None] = "right", 133 | *, 134 | day_anchor: u.Day_Anchor = "SUN", 135 | month_anchor: u.Month_Anchor = "JAN", 136 | ) -> DatetimeIndex: 137 | 138 | start, end, periods = u._error_check_htf_range(start, end, periods) 139 | 140 | # Cap Beginning and end dates to the opening date of IEX 141 | if start is not None: 142 | start = max(start, Timestamp("2013-08-25")) 143 | if end is not None: 144 | end = max(end, Timestamp("2013-08-25")) 145 | 146 | return u.date_range_htf( 147 | self.holidays(), 148 | frequency, 149 | start, 150 | end, 151 | periods, 152 | closed, 153 | day_anchor=day_anchor, 154 | month_anchor=month_anchor, 155 | ) 156 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/jpx.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | from itertools import chain 3 | 4 | from pandas.tseries.holiday import AbstractHolidayCalendar 5 | from zoneinfo import ZoneInfo 6 | 7 | from pandas_market_calendars.holidays.jp import * 8 | from pandas_market_calendars.holidays.us import USNewYearsDay 9 | from pandas_market_calendars.market_calendar import MarketCalendar 10 | 11 | 12 | # TODO: 13 | # From 1949 to 1972 the TSE was open on all non-holiday Saturdays for a half day 14 | # From 1973 to 1984 the TSE was open on all non-holiday Saturdays except the third Saturday of the month 15 | # need to add support for weekmask to make this work properly 16 | 17 | 18 | class JPXExchangeCalendar(MarketCalendar): 19 | """ 20 | Exchange calendar for JPX 21 | 22 | Open Time: 9:31 AM, Asia/Tokyo 23 | LUNCH BREAK :facepalm: : 11:30 AM - 12:30 PM Asia/Tokyo 24 | Close Time: 3:30 PM, Asia/Tokyo 25 | 26 | Market close of Japan changed from 3:00 PM to 3:30 PM on November 5, 2024 27 | Reference: 28 | https://www.jpx.co.jp/english/equities/trading/domestic/tvdivq0000006blj-att/tradinghours_eg.pdf 29 | """ 30 | 31 | aliases = ["JPX", "XJPX"] 32 | regular_market_times = { 33 | "market_open": ((None, time(9)),), 34 | "market_close": ((None, time(15)), ("2024-11-05", time(15, 30))), 35 | "break_start": ((None, time(11, 30)),), 36 | "break_end": ((None, time(12, 30)),), 37 | } 38 | regular_early_close = time(13) 39 | 40 | @property 41 | def name(self): 42 | return "JPX" 43 | 44 | @property 45 | def full_name(self): 46 | return "Japan Exchange Group" 47 | 48 | @property 49 | def tz(self): 50 | return ZoneInfo("Asia/Tokyo") 51 | 52 | @property 53 | def adhoc_holidays(self): 54 | return list( 55 | chain( 56 | AscensionDays, 57 | MarriageDays, 58 | FuneralShowa, 59 | EnthronementDays, 60 | AutumnalCitizenDates, 61 | NoN225IndexPrices, 62 | EquityTradingSystemFailure, 63 | ) 64 | ) 65 | 66 | @property 67 | def regular_holidays(self): 68 | return AbstractHolidayCalendar( 69 | rules=[ 70 | USNewYearsDay, 71 | JapanNewYearsDay2, 72 | JapanNewYearsDay3, 73 | JapanComingOfAgeDay1951To1973, 74 | JapanComingOfAgeDay1974To1999, 75 | JapanComingOfAgeDay, 76 | JapanNationalFoundationDay1969To1973, 77 | JapanNationalFoundationDay, 78 | JapanEmperorsBirthday, 79 | JapanVernalEquinox, 80 | JapanShowaDayUntil1972, 81 | JapanShowaDay, 82 | JapanConstitutionMemorialDayUntil1972, 83 | JapanConstitutionMemorialDay, 84 | JapanGreeneryDay, 85 | JapanChildrensDayUntil1972, 86 | JapanChildrensDay, 87 | JapanGoldenWeekBonusDay, 88 | JapanMarineDay1996To2002, 89 | JapanMarineDay2003To2019, 90 | JapanMarineDay2020, 91 | JapanMarineDay2021, 92 | JapanMarineDay, 93 | JapanMountainDay2016to2019, 94 | JapanMountainDay2020, 95 | JapanMountainDay2021, 96 | JapanMountainDay2021NextDay, 97 | JapanMountainDay, 98 | JapanRespectForTheAgedDay1966To1972, 99 | JapanRespectForTheAgedDay1973To2002, 100 | JapanRespectForTheAgedDay, 101 | JapanAutumnalEquinox, 102 | JapanHealthAndSportsDay1966To1972, 103 | JapanHealthAndSportsDay1973To1999, 104 | JapanHealthAndSportsDay2000To2019, 105 | JapanSportsDay2020, 106 | JapanSportsDay2021, 107 | JapanSportsDay, 108 | JapanCultureDayUntil1972, 109 | JapanCultureDay, 110 | JapanLaborThanksgivingDayUntil1972, 111 | JapanLaborThanksgivingDay, 112 | JapanEmperorAkahitosBirthday, 113 | JapanDecember29Until1988, 114 | JapanDecember30Until1988, 115 | JapanBeforeNewYearsDay, 116 | ] 117 | ) 118 | -------------------------------------------------------------------------------- /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 | from datetime import time 17 | 18 | from pandas.tseries.holiday import AbstractHolidayCalendar, EasterMonday, GoodFriday 19 | from zoneinfo import ZoneInfo 20 | 21 | from pandas_market_calendars.holidays.uk import ( 22 | BoxingDay, 23 | Christmas, 24 | ChristmasEve, 25 | LSENewYearsDay, 26 | LSENewYearsEve, 27 | MayBank_pre_1995, 28 | MayBank_post_1995_pre_2020, 29 | MayBank_post_2020, 30 | SpringBank_pre_2002, 31 | SpringBank_post_2002_pre_2012, 32 | SpringBank_post_2012_pre_2022, 33 | SpringBank_post_2022, 34 | SummerBank, 35 | WeekendBoxingDay, 36 | WeekendChristmas, 37 | UniqueCloses, 38 | ) 39 | from pandas_market_calendars.market_calendar import MarketCalendar 40 | 41 | 42 | class LSEExchangeCalendar(MarketCalendar): 43 | """ 44 | Exchange calendar for the London Stock Exchange 45 | 46 | Open Time: 8:00 AM, GMT 47 | Close Time: 4:30 PM, GMT 48 | 49 | Regularly-Observed Holidays: 50 | - New Years Day (observed on first business day on/after) 51 | - Good Friday 52 | - Easter Monday 53 | - Early May Bank Holiday (first Monday in May) 54 | - Spring Bank Holiday (last Monday in May) 55 | - Summer Bank Holiday (last Monday in August) 56 | - Christmas Day 57 | - Dec. 27th (if Christmas is on a weekend) 58 | - Boxing Day 59 | - Dec. 28th (if Boxing Day is on a weekend) 60 | """ 61 | 62 | aliases = ["LSE"] 63 | regular_market_times = { 64 | "market_open": ((None, time(8)),), 65 | "market_close": ((None, time(16, 30)),), 66 | } 67 | 68 | @property 69 | def name(self): 70 | return "LSE" 71 | 72 | @property 73 | def full_name(self): 74 | return "London Stock Exchange" 75 | 76 | @property 77 | def tz(self): 78 | return ZoneInfo("Europe/London") 79 | 80 | @property 81 | def regular_holidays(self): 82 | return AbstractHolidayCalendar( 83 | rules=[ 84 | LSENewYearsDay, 85 | GoodFriday, 86 | EasterMonday, 87 | MayBank_pre_1995, 88 | MayBank_post_1995_pre_2020, 89 | MayBank_post_2020, 90 | SpringBank_pre_2002, 91 | SpringBank_post_2002_pre_2012, 92 | SpringBank_post_2012_pre_2022, 93 | SpringBank_post_2022, 94 | SummerBank, 95 | Christmas, 96 | WeekendChristmas, 97 | BoxingDay, 98 | WeekendBoxingDay, 99 | ] 100 | ) 101 | 102 | @property 103 | def adhoc_holidays(self): 104 | return UniqueCloses 105 | 106 | @property 107 | def special_closes(self): 108 | return [ 109 | ( 110 | time(12, 30), 111 | AbstractHolidayCalendar( 112 | rules=[ 113 | ChristmasEve, 114 | LSENewYearsEve, 115 | ] 116 | ), 117 | ) 118 | ] 119 | -------------------------------------------------------------------------------- /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( 41 | (t[0], t[1], self._ec.open_offset) 42 | for t in cls.regular_market_times["market_open"] 43 | ), 44 | ) 45 | 46 | if self._ec.close_offset: 47 | cls.regular_market_times._set( 48 | "market_close", 49 | tuple((t[0], t[1], self._ec.close_offset) for t in cls.regular_market_times["market_close"]), 50 | ) 51 | cls._FINALIZE_TRADING_CALENDAR = False 52 | 53 | self.__init__(*args, **kwargs) 54 | return self 55 | 56 | def __init__(self, open_time=None, close_time=None): 57 | super().__init__(open_time, close_time) 58 | 59 | @property 60 | def ec(self): 61 | if self._EC_NOT_INITIALIZED: 62 | self._ec.__init__() 63 | self._EC_NOT_INITIALIZED = False 64 | 65 | return self._ec 66 | 67 | @property 68 | def name(self): 69 | return self._ec.name 70 | 71 | @property 72 | def full_name(self): 73 | return self._ec.name 74 | 75 | @property 76 | def tz(self): 77 | return self._ec.tz 78 | 79 | @property 80 | def regular_holidays(self): 81 | return self._ec.regular_holidays 82 | 83 | @property 84 | def adhoc_holidays(self): 85 | return self._ec.adhoc_holidays 86 | 87 | @property 88 | def special_opens(self): 89 | return self._ec.special_opens 90 | 91 | @property 92 | def special_opens_adhoc(self): 93 | return self._ec.special_opens_adhoc 94 | 95 | @property 96 | def special_closes(self): 97 | return self._ec.special_closes 98 | 99 | @property 100 | def special_closes_adhoc(self): 101 | return self._ec.special_closes_adhoc 102 | 103 | @property 104 | def weekmask(self): 105 | if hasattr(self._ec, "weekmask"): 106 | if "1" in self._ec.weekmask or "0" in self._ec.weekmask: 107 | # Convert 1s & 0s to Day Abbreviations 108 | return " ".join([DAYMASKS[i] for i, val in enumerate(self._ec.weekmask) if val == "1"]) 109 | else: 110 | return self._ec.weekmask 111 | else: 112 | return "Mon Tue Wed Thu Fri" 113 | 114 | 115 | calendars = exchange_calendars.calendar_utils._default_calendar_factories # noqa 116 | 117 | time_props = dict( 118 | open_times="market_open", 119 | close_times="market_close", 120 | break_start_times="break_start", 121 | break_end_times="break_end", 122 | ) 123 | 124 | for exchange in calendars: 125 | cal = calendars[exchange] 126 | 127 | # this loop will set up the newly required regular_market_times dictionary 128 | regular_market_times = {} 129 | for prop, new in time_props.items(): 130 | times = getattr(cal, prop) 131 | if times is None or isinstance(times, property): 132 | continue 133 | regular_market_times[new] = times 134 | 135 | cal = type( 136 | exchange, 137 | (TradingCalendar,), 138 | { 139 | "_ec_class": calendars[exchange], 140 | "alias": [exchange], 141 | "regular_market_times": regular_market_times, 142 | }, 143 | ) 144 | locals()[f"{exchange}ExchangeCalendar"] = cal 145 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/ose.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | from pandas.tseries.holiday import ( 4 | AbstractHolidayCalendar, 5 | EasterMonday, 6 | GoodFriday, 7 | Holiday, 8 | ) 9 | from pandas.tseries.offsets import Day, Easter 10 | from zoneinfo import ZoneInfo 11 | 12 | from pandas_market_calendars.market_calendar import MarketCalendar 13 | 14 | OSENewYearsDay = Holiday("New Year's Day", month=1, day=1) 15 | 16 | OSEWednesdayBeforeEaster = Holiday( 17 | "Wednesday before Easter", month=1, day=1, offset=[Easter(), Day(-4)] 18 | ) 19 | 20 | OSEMaundyThursday = Holiday("Maundy Thursday", month=1, day=1, offset=[Easter(), Day(-3)]) 21 | 22 | OSEGoodFriday = GoodFriday 23 | 24 | OSEEasterMonday = EasterMonday 25 | 26 | OSELabourDay = Holiday("Labour Day", month=5, day=1) 27 | 28 | OSEConstitutionDay = Holiday("Constitution Day", month=5, day=17) 29 | 30 | OSEWhitMonday = Holiday("Whit Monday", month=1, day=1, offset=[Easter(), Day(50)]) 31 | 32 | OSEAscensionDay = Holiday("Ascension Day", month=1, day=1, offset=[Easter(), Day(39)]) 33 | 34 | OSEChristmasEve = Holiday( 35 | "Christmas Eve", 36 | month=12, 37 | day=24, 38 | ) 39 | 40 | OSEChristmasDay = Holiday("Christmas Day", month=12, day=25) 41 | 42 | OSEBoxingDay = Holiday("Boxing Day", month=12, day=26) 43 | 44 | OSENewYearsEve = Holiday("New Year's Eve", month=12, day=31) 45 | 46 | 47 | class OSEExchangeCalendar(MarketCalendar): 48 | """ 49 | Exchange calendar for Oslo Stock Exchange 50 | 51 | Note these dates are only checked against 2017, 2018 and 2019 52 | https://www.oslobors.no/ob_eng/Oslo-Boers/About-Oslo-Boers/Opening-hours 53 | 54 | Opening times for the regular trading of equities (not including closing auction call) 55 | Open Time: 9:00 AM, CEST/EST 56 | Close Time: 4:20 PM, CEST/EST 57 | 58 | Regularly-Observed Holidays (not necessarily in order): 59 | - New Years Day 60 | - Wednesday before Easter (Half trading day) 61 | - Maundy Thursday 62 | - Good Friday 63 | - Easter Monday 64 | - Labour Day 65 | - Ascension Day 66 | - Constitution Day 67 | - Whit Monday 68 | - Christmas Eve 69 | - Christmas Day 70 | - Boxing Day 71 | - New Year's Eve 72 | """ 73 | 74 | aliases = ["OSE"] 75 | regular_market_times = { 76 | "market_open": ((None, time(9)),), 77 | "market_close": ((None, time(16, 20)),), 78 | } 79 | 80 | @property 81 | def name(self): 82 | return "OSE" 83 | 84 | @property 85 | def full_name(self): 86 | return "Oslo Stock Exchange" 87 | 88 | @property 89 | def tz(self): 90 | return ZoneInfo("Europe/Oslo") 91 | 92 | @property 93 | def regular_holidays(self): 94 | return AbstractHolidayCalendar( 95 | rules=[ 96 | OSENewYearsDay, 97 | OSEMaundyThursday, 98 | OSEGoodFriday, 99 | OSEEasterMonday, 100 | OSELabourDay, 101 | OSEConstitutionDay, 102 | OSEWhitMonday, 103 | OSEAscensionDay, 104 | OSEChristmasEve, 105 | OSEChristmasDay, 106 | OSEBoxingDay, 107 | OSENewYearsEve, 108 | ] 109 | ) 110 | 111 | @property 112 | def special_closes(self): 113 | return [ 114 | ( 115 | time(13, 0, tzinfo=self.tz), 116 | AbstractHolidayCalendar(rules=[OSEWednesdayBeforeEaster]), 117 | ) 118 | ] 119 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/six.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | from pandas.tseries.holiday import ( 4 | AbstractHolidayCalendar, 5 | Day, 6 | Easter, 7 | EasterMonday, 8 | GoodFriday, 9 | Holiday, 10 | previous_friday, 11 | ) 12 | from zoneinfo import ZoneInfo 13 | 14 | from pandas_market_calendars.market_calendar import ( 15 | FRIDAY, 16 | MONDAY, 17 | MarketCalendar, 18 | THURSDAY, 19 | TUESDAY, 20 | WEDNESDAY, 21 | ) 22 | 23 | # New Year's Eve 24 | NewYearsEve = Holiday( 25 | "New Year's Eve", 26 | month=12, 27 | day=31, 28 | observance=previous_friday, 29 | ) 30 | # New Year's Day 31 | NewYearsDay = Holiday( 32 | "New Year's Day", 33 | month=1, 34 | day=1, 35 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 36 | ) 37 | # Berthold's Day 38 | BertholdsDay = Holiday( 39 | "Berthold's Day", 40 | month=1, 41 | day=2, 42 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 43 | ) 44 | # Early May bank holiday 45 | MayBank = Holiday( 46 | "Early May Bank Holiday", 47 | month=5, 48 | day=1, 49 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 50 | ) 51 | # Ascension Day (Auffahrt) 52 | AscensionDay = Holiday( 53 | "Ascension Day", 54 | month=1, 55 | day=1, 56 | offset=[Easter(), Day(39)], 57 | days_of_week=(THURSDAY,), 58 | ) 59 | # Pentecost Day (Pfingstmontag) 60 | PentecostMonday = Holiday( 61 | "Pentecost Monday", 62 | month=1, 63 | day=1, 64 | offset=[Easter(), Day(50)], 65 | days_of_week=(MONDAY,), 66 | ) 67 | # Swiss National Day 68 | SwissNationalDay = Holiday( 69 | "Swiss National Day", 70 | month=8, 71 | day=1, 72 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 73 | ) 74 | # Christmas Eve 75 | ChristmasEve = Holiday( 76 | "Christmas Eve", 77 | month=12, 78 | day=24, 79 | ) 80 | # Christmas 81 | Christmas = Holiday( 82 | "Christmas", 83 | month=12, 84 | day=25, 85 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 86 | ) 87 | # Boxing day 88 | BoxingDay = Holiday( 89 | "Boxing Day", 90 | month=12, 91 | day=26, 92 | ) 93 | 94 | 95 | class SIXExchangeCalendar(MarketCalendar): 96 | """ 97 | Exchange calendar for SIX 98 | 99 | """ 100 | 101 | aliases = ["SIX"] 102 | regular_market_times = { 103 | "market_open": ((None, time(9)),), 104 | "market_close": ((None, time(17, 30)),), 105 | } 106 | 107 | @property 108 | def name(self): 109 | return "SIX" 110 | 111 | @property 112 | def full_name(self): 113 | return "SIX Swiss Exchange" 114 | 115 | @property 116 | def tz(self): 117 | return ZoneInfo("Europe/Zurich") 118 | 119 | @property 120 | def regular_holidays(self): 121 | return AbstractHolidayCalendar( 122 | rules=[ 123 | NewYearsDay, 124 | BertholdsDay, 125 | GoodFriday, 126 | EasterMonday, 127 | MayBank, 128 | AscensionDay, 129 | PentecostMonday, 130 | SwissNationalDay, 131 | ChristmasEve, 132 | Christmas, 133 | BoxingDay, 134 | NewYearsEve, 135 | ] 136 | ) 137 | -------------------------------------------------------------------------------- /pandas_market_calendars/calendars/tsx.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | from itertools import chain 3 | 4 | import pandas as pd 5 | from pandas.tseries.holiday import ( 6 | AbstractHolidayCalendar, 7 | DateOffset, 8 | GoodFriday, 9 | Holiday, 10 | MO, 11 | weekend_to_monday, 12 | ) 13 | from zoneinfo import ZoneInfo 14 | 15 | from pandas_market_calendars.holidays.uk import ( 16 | BoxingDay, 17 | WeekendBoxingDay, 18 | WeekendChristmas, 19 | ) 20 | from pandas_market_calendars.market_calendar import ( 21 | MarketCalendar, 22 | MONDAY, 23 | TUESDAY, 24 | WEDNESDAY, 25 | THURSDAY, 26 | FRIDAY, 27 | ) 28 | 29 | # New Year's Day 30 | TSXNewYearsDay = Holiday( 31 | "New Year's Day", 32 | month=1, 33 | day=1, 34 | observance=weekend_to_monday, 35 | ) 36 | # Ontario Family Day 37 | FamilyDay = Holiday( 38 | "Family Day", 39 | month=2, 40 | day=1, 41 | offset=DateOffset(weekday=MO(3)), 42 | start_date="2008-01-01", 43 | ) 44 | # Victoria Day 45 | # https://www.timeanddate.com/holidays/canada/victoria-day 46 | VictoriaDay = Holiday( 47 | "Victoria Day", 48 | month=5, 49 | day=24, 50 | offset=DateOffset(weekday=MO(-1)), 51 | ) 52 | # Canada Day 53 | CanadaDay = Holiday( 54 | "Canada Day", 55 | month=7, 56 | day=1, 57 | observance=weekend_to_monday, 58 | ) 59 | # Civic Holiday 60 | CivicHoliday = Holiday( 61 | "Civic Holiday", 62 | month=8, 63 | day=1, 64 | offset=DateOffset(weekday=MO(1)), 65 | ) 66 | # Labor Day 67 | LaborDay = Holiday( 68 | "Labor Day", 69 | month=9, 70 | day=1, 71 | offset=DateOffset(weekday=MO(1)), 72 | ) 73 | # Thanksgiving 74 | Thanksgiving = Holiday( 75 | "Thanksgiving", 76 | month=10, 77 | day=1, 78 | offset=DateOffset(weekday=MO(2)), 79 | ) 80 | 81 | Christmas = Holiday( 82 | "Christmas", 83 | month=12, 84 | day=25, 85 | ) 86 | 87 | ChristmasEveEarlyClose2010Onwards = Holiday( 88 | "Christmas Eve Early Close", 89 | month=12, 90 | day=24, 91 | days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY), 92 | start_date=pd.Timestamp("2010-01-01"), 93 | ) 94 | 95 | September11Closings2001 = [ 96 | pd.Timestamp("2001-09-11", tz="UTC"), 97 | pd.Timestamp("2001-09-12", tz="UTC"), 98 | ] 99 | 100 | 101 | class TSXExchangeCalendar(MarketCalendar): 102 | """ 103 | Exchange calendar for the Toronto Stock Exchange 104 | 105 | Open Time: 9:30 AM, EST 106 | Close Time: 4:00 PM, EST 107 | 108 | Regularly-Observed Holidays: 109 | - New Years Day (observed on first business day on/after) 110 | - Family Day (Third Monday in February, starting in 2008) 111 | - Good Friday 112 | - Victoria Day (Monday before May 25th) 113 | - Canada Day (July 1st, observed first business day after) 114 | - Civic Holiday (First Monday in August) 115 | - Labor Day (First Monday in September) 116 | - Thanksgiving (Second Monday in October) 117 | - Christmas Day 118 | - Dec. 26th if Christmas is on a Sunday 119 | - Dec. 27th if Christmas is on a weekend 120 | - Boxing Day 121 | - Dec. 27th if Christmas is on a Sunday 122 | - Dec. 28th if Boxing Day is on a weekend 123 | 124 | Early closes: 125 | - Starting in 2010, if Christmas Eve falls on a weekday, the market 126 | closes at 1:00 pm that day. If it falls on a weekend, there is no 127 | early close. 128 | """ 129 | 130 | aliases = ["TSX", "TSXV"] 131 | 132 | regular_market_times = { 133 | "market_open": ((None, time(9, 30)),), 134 | "market_close": ((None, time(16)),), 135 | } 136 | 137 | @property 138 | def name(self): 139 | return "TSX" 140 | 141 | @property 142 | def full_name(self): 143 | return "Toronto Stock Exchange" 144 | 145 | @property 146 | def tz(self): 147 | return ZoneInfo("Canada/Eastern") 148 | 149 | regular_early_close = time(13) 150 | 151 | @property 152 | def regular_holidays(self): 153 | return AbstractHolidayCalendar( 154 | rules=[ 155 | TSXNewYearsDay, 156 | FamilyDay, 157 | GoodFriday, 158 | VictoriaDay, 159 | CanadaDay, 160 | CivicHoliday, 161 | LaborDay, 162 | Thanksgiving, 163 | Christmas, 164 | WeekendChristmas, 165 | BoxingDay, 166 | WeekendBoxingDay, 167 | ] 168 | ) 169 | 170 | @property 171 | def adhoc_holidays(self): 172 | return list( 173 | chain( 174 | September11Closings2001, 175 | ) 176 | ) 177 | 178 | @property 179 | def special_closes(self): 180 | return [ 181 | ( 182 | self.regular_early_close, 183 | AbstractHolidayCalendar([ChristmasEveEarlyClose2010Onwards]), 184 | ) 185 | ] 186 | -------------------------------------------------------------------------------- /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( 18 | name, cls._regmeta_class_registry.keys() 19 | ) 20 | ) 21 | return class_(*args, **kwargs) 22 | 23 | 24 | def _regmeta_register_class(cls, regcls, name): 25 | """ 26 | :param cls(RegisteryMeta): registration base class 27 | :param regcls(class): class to be registered 28 | :param name(str): name of the class to be registered 29 | """ 30 | if hasattr(regcls, "aliases"): 31 | if regcls.aliases: 32 | for alias in regcls.aliases: 33 | cls._regmeta_class_registry[alias] = regcls 34 | else: 35 | cls._regmeta_class_registry[name] = regcls 36 | else: 37 | cls._regmeta_class_registry[name] = regcls 38 | 39 | 40 | class RegisteryMeta(type): 41 | """ 42 | Metaclass used to register all classes inheriting from RegisteryMeta 43 | """ 44 | 45 | def __new__(mcs, name, bases, attr): 46 | cls = super(RegisteryMeta, mcs).__new__(mcs, name, bases, attr) 47 | if not hasattr(cls, "_regmeta_class_registry"): 48 | cls._regmeta_class_registry = {} 49 | cls.factory = classmethod(_regmeta_instance_factory) 50 | 51 | return cls 52 | 53 | def __init__(cls, name, bases, attr): 54 | if not inspect.isabstract(cls): 55 | _regmeta_register_class(cls, cls, name) 56 | for b in bases: 57 | if hasattr(b, "_regmeta_class_registry"): 58 | _regmeta_register_class(b, cls, name) 59 | 60 | super(RegisteryMeta, cls).__init__(name, bases, attr) 61 | 62 | cls.regular_market_times = ProtectedDict(cls.regular_market_times) 63 | cls.open_close_map = ProtectedDict(cls.open_close_map) 64 | 65 | cls.special_market_open = cls.special_opens 66 | cls.special_market_open_adhoc = cls.special_opens_adhoc 67 | 68 | cls.special_market_close = cls.special_closes 69 | cls.special_market_close_adhoc = cls.special_closes_adhoc 70 | 71 | 72 | class ProtectedDict(dict): 73 | def __init__(self, *args, **kwargs): 74 | super().__init__(*args, **kwargs) 75 | # __init__ is bypassed when unpickling, which causes __setitem__ to fail 76 | # without the _INIT_RAN_NORMALLY flag 77 | self._INIT_RAN_NORMALLY = True 78 | 79 | def _set(self, key, value): 80 | return super().__setitem__(key, value) 81 | 82 | def _del(self, key): 83 | return super().__delitem__(key) 84 | 85 | def __setitem__(self, key, value): 86 | if not hasattr(self, "_INIT_RAN_NORMALLY"): 87 | return self._set(key, value) 88 | 89 | raise TypeError( 90 | "You cannot set a value directly, you can change regular_market_times " 91 | "using .change_time, .add_time or .remove_time." 92 | ) 93 | 94 | def __delitem__(self, key): 95 | if not hasattr(self, "_INIT_RAN_NORMALLY"): 96 | return self._del(key) 97 | 98 | raise TypeError( 99 | "You cannot delete an item directly. You can change regular_market_times " 100 | "using .change_time, .add_time or .remove_time" 101 | ) 102 | 103 | def __repr__(self): 104 | return self.__class__.__name__ + "(" + super().__repr__() + ")" 105 | 106 | def __str__(self): 107 | try: 108 | formatted = pformat(dict(self), sort_dicts=False) # sort_dicts apparently not available < python3.8 109 | except TypeError: 110 | formatted = pformat(dict(self)) 111 | 112 | return self.__class__.__name__ + "(\n" + formatted + "\n)" 113 | 114 | def copy(self): 115 | return self.__class__(super().copy()) 116 | -------------------------------------------------------------------------------- /pandas_market_calendars/holidays/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsheftel/pandas_market_calendars/0b574d560738665970e532e94ea9584522fc09a0/pandas_market_calendars/holidays/__init__.py -------------------------------------------------------------------------------- /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/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 | ) 10 | 11 | # New Year's Day 12 | OZNewYearsDay = Holiday( 13 | "New Year's Day", 14 | month=1, 15 | day=1, 16 | observance=weekend_to_monday, 17 | ) 18 | 19 | # Australia Day 20 | AustraliaDay = Holiday( 21 | "Australia Day", 22 | month=1, 23 | day=26, 24 | observance=weekend_to_monday, 25 | ) 26 | 27 | # ANZAC Day 28 | AnzacDay = Holiday( 29 | "ANZAC Day", 30 | month=4, 31 | day=25, 32 | ) 33 | 34 | # Queen's Birthday 35 | QueensBirthday = Holiday( 36 | "Queen's Birthday", 37 | month=6, 38 | day=1, 39 | offset=DateOffset(weekday=MO(2)), 40 | ) 41 | 42 | # Christmas 43 | Christmas = Holiday( 44 | "Christmas", 45 | month=12, 46 | day=25, 47 | observance=weekend_to_monday, 48 | ) 49 | 50 | # Boxing day 51 | BoxingDay = Holiday( 52 | "Boxing Day", 53 | month=12, 54 | day=26, 55 | observance=next_monday_or_tuesday, 56 | ) 57 | 58 | # One-off holiday additions and removals in Australia 59 | 60 | UniqueCloses = [] 61 | 62 | # National Day of Mourning for Her Majesty the Queen 63 | UniqueCloses.append(Timestamp("2022-09-22", tz="UTC")) 64 | -------------------------------------------------------------------------------- /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( 173 | pd.Timestamp("1973-11-14", tz="UTC") 174 | ) # Wedding Day of Princess Anne and Mark Phillips 175 | UniqueCloses.append( 176 | pd.Timestamp("1981-07-29", tz="UTC") 177 | ) # Wedding Day of Prince Charles and Diana Spencer 178 | UniqueCloses.append( 179 | pd.Timestamp("2011-04-29", tz="UTC") 180 | ) # Wedding Day of Prince William and Catherine Middleton 181 | 182 | # Coronation of King Charles III 183 | UniqueCloses.append(pd.Timestamp("2023-05-08", tz="UTC")) 184 | 185 | # Miscellaneous 186 | UniqueCloses.append(pd.Timestamp("1999-12-31", tz="UTC")) # Eve of 3rd Millenium A.D. 187 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pandas_market_calendars" 3 | version = "5.1.0" 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.9', 21 | 'Programming Language :: Python :: 3.10', 22 | 'Programming Language :: Python :: 3.11', 23 | 'Programming Language :: Python :: 3.12', 24 | 'Programming Language :: Python :: 3.13', 25 | ] 26 | dependencies = ['pandas>=1.1', 'tzdata', 'python-dateutil', 'exchange-calendars>=3.3'] 27 | 28 | [project.optional-dependencies] 29 | dev = ['pytest', 'black', 'pre-commit', 'build'] 30 | 31 | [build-system] 32 | requires = ["setuptools>=61.0", "wheel"] 33 | build-backend = "setuptools.build_meta" 34 | 35 | [tool.setuptools] 36 | packages = ["pandas_market_calendars", "pandas_market_calendars.calendars", "pandas_market_calendars.holidays"] 37 | 38 | [project.urls] 39 | "Homepage" = "https://github.com/rsheftel/pandas_market_calendars" 40 | "Source" = "https://github.com/rsheftel/pandas_market_calendars" 41 | "Documentation" = "https://pandas-market-calendars.readthedocs.io/en/latest/" 42 | "Changelog" = "https://pandas-market-calendars.readthedocs.io/en/latest/change_log.html" 43 | "Bug Tracker" = "https://github.com/rsheftel/pandas_market_calendars/issues" 44 | 45 | [tool.coverage.run] 46 | branch = true 47 | 48 | [tool.coverage.report] 49 | exclude_also = [ 50 | # Don't complain about missing debug-only code: 51 | "def __repr__", 52 | "if self\\.debug", 53 | # Don't complain if tests don't hit defensive assertion code: 54 | "raise AssertionError", 55 | "raise NotImplementedError", 56 | # Don't complain if non-runnable code isn't run: 57 | "if 0:", 58 | "if __name__ == .__main__.:", 59 | # Don't complain about abstract methods, they aren't run: 60 | "@(abc\\.)?abstractmethod", 61 | ] 62 | 63 | ignore_errors = true 64 | 65 | [tool.black] 66 | line-length = 120 67 | 68 | [tool.isort] 69 | profile = "black" 70 | line_length = 88 71 | skip_gitignore = true 72 | skip_glob = ["tests/data", "profiling"] 73 | known_first_party = ["black", "blib2to3", "blackd", "_black_version"] 74 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsheftel/pandas_market_calendars/0b574d560738665970e532e94ea9584522fc09a0/tests/__init__.py -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/test_asx_calendar.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | 3 | import pandas as pd 4 | from zoneinfo import ZoneInfo 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 = { 44 | i: {"closed": None, "open": None} for i in australia_unique_hols_names 45 | } 46 | 47 | # One-off holiday additions and removals in Australia 48 | 49 | # National Day of Mourning for Her Majesty the Queen 50 | australia_unique_hols["QEII_DayOfMourning"]["closed"] = [pd.Timestamp("2022-09-22")] 51 | 52 | # Test of closed dates 53 | asx = ASXExchangeCalendar() 54 | # get all the closed dates 55 | closed_days = [australia_unique_hols[k].get("closed") for k in australia_unique_hols] 56 | good_dates = asx.valid_days("1990-01-01", "2022-12-31") 57 | for date in chain.from_iterable(closed_days): 58 | assert pd.Timestamp(date, tz="UTC") not in good_dates 59 | 60 | # Test of open dates 61 | open_days = [australia_unique_hols[k].get("open") for k in australia_unique_hols] 62 | open_days = [i for i in open_days if i] 63 | good_dates = asx.valid_days("1990-01-01", "2022-12-31") 64 | for date in chain.from_iterable(open_days): 65 | assert pd.Timestamp(date, tz="UTC") in good_dates 66 | -------------------------------------------------------------------------------- /tests/test_bmf_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pandas as pd 4 | from zoneinfo import ZoneInfo 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 ( 33 | pd.Timestamp(datetime.date(year, month, day), tz="UTC").to_datetime64() 34 | in holidays 35 | ) 36 | for year in range(2022, 2040): 37 | for month, day in [(1, 25), (7, 9)]: 38 | assert pd.Timestamp(datetime.date(year, month, day), tz="UTC").to_datetime64() not in holidays 39 | for year in range(2022, 2024): 40 | for month, day in [(11, 20)]: 41 | assert pd.Timestamp(datetime.date(year, month, day), tz="UTC").to_datetime64() not in holidays 42 | 43 | 44 | def test_sunday_new_years_eve(): 45 | # All instances of December 29th on a Friday should be holidays 46 | 47 | holidays = BMFExchangeCalendar().holidays().holidays 48 | 49 | for year in range(1994, 2040): 50 | date = pd.Timestamp(datetime.date(year, 12, 29), tz="UTC") 51 | if date.day_of_week == 4: 52 | # December 29th on a Friday 53 | 54 | assert date.to_datetime64() in holidays 55 | 56 | 57 | def test_post_2022_nov20(): 58 | # November 20th national holiday should be present from 2024 59 | 60 | holidays = BMFExchangeCalendar().holidays().holidays 61 | 62 | for year in range(2024, 2040): 63 | assert pd.Timestamp(datetime.date(year, 11, 20), tz="UTC").to_datetime64() in holidays 64 | -------------------------------------------------------------------------------- /tests/test_bse_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pandas as pd 4 | import pytest 5 | from zoneinfo import ZoneInfo 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( 19 | pd.Timestamp("2004-01-01"), pd.Timestamp("2018-12-31") 20 | ) 21 | for session_label in BSEClosedDay: 22 | assert session_label not in trading_days 23 | 24 | 25 | def test_open_close_time(): 26 | bse_calendar = BSEExchangeCalendar() 27 | india_time_zone = ZoneInfo("Asia/Calcutta") 28 | 29 | bse_schedule = bse_calendar.schedule( 30 | start_date=datetime.datetime(2015, 1, 14, tzinfo=india_time_zone), 31 | end_date=datetime.datetime(2015, 1, 16, tzinfo=india_time_zone), 32 | ) 33 | 34 | assert bse_calendar.open_at_time( 35 | schedule=bse_schedule, 36 | timestamp=datetime.datetime(2015, 1, 14, 11, 0, tzinfo=india_time_zone), 37 | ) 38 | 39 | with pytest.raises(ValueError): 40 | bse_calendar.open_at_time( 41 | schedule=bse_schedule, 42 | timestamp=datetime.datetime(2015, 1, 9, 12, 0, tzinfo=india_time_zone), 43 | ) 44 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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( 107 | "class 12a", "0", "1", "2", "a", kw0="k0", kw1="k1", kw2="k2", kw12a="k12a" 108 | ) 109 | assert (o.arg0, o.arg1, o.arg2, o.arg12a, o.kw0, o.kw1, o.kw2, o.kw12a) == ( 110 | "0", 111 | "1", 112 | "2", 113 | "a", 114 | "k0", 115 | "k1", 116 | "k2", 117 | "k12a", 118 | ) 119 | assert Class12a == o.__class__ 120 | 121 | o = factory2("class 2", "0", "2", kw0="k0", kw2="k2") 122 | assert (o.arg0, o.arg2, o.kw0, o.kw2) == ("0", "2", "k0", "k2") 123 | assert Class2 == o.__class__ 124 | 125 | 126 | def test_metamixing(): 127 | BaseMeta = type("BaseMeta", (ABCMeta, RegisteryMeta), {}) 128 | 129 | class Base(metaclass=BaseMeta): 130 | regular_market_times = { 131 | "market_open": {None: time(0)}, 132 | "market_close": {None: time(23)}, 133 | } 134 | open_close_map = {} 135 | 136 | @classmethod 137 | def _prepare_regular_market_times(cls): 138 | return 139 | 140 | def special_opens(self): 141 | return [] 142 | 143 | special_closes = special_closes_adhoc = special_opens_adhoc = special_opens 144 | 145 | @abstractmethod 146 | def test(self): 147 | pass 148 | 149 | class Class1(Base): 150 | aliases = ["c1", "c 1"] 151 | 152 | def test(self): 153 | return 123 154 | 155 | try: 156 | Base() 157 | except TypeError: 158 | pass 159 | else: 160 | raise RuntimeError("Abstract class is instantiated") 161 | 162 | o1 = Base.factory("c1") 163 | o2 = Base.factory("c 1") 164 | assert o1.test() == o2.test() 165 | 166 | with pytest.raises(RuntimeError): 167 | Base.factory("error") # doesn't exist 168 | 169 | class Class2(Base): # no aliases 170 | def test(self): 171 | return "test" 172 | 173 | assert Base.factory("Class2").test() == "test" 174 | 175 | 176 | def test_protected_dict(): 177 | dct = ProtectedDict(dict(a=1, b=2)) 178 | 179 | with pytest.raises(TypeError): 180 | dct["a"] = 2 181 | 182 | with pytest.raises(TypeError): 183 | del dct["b"] 184 | 185 | del dct._INIT_RAN_NORMALLY 186 | del dct["b"] 187 | 188 | dct = ProtectedDict(dict(a=1, b=2)) 189 | 190 | s = "ProtectedDict(\n" + pformat(dict(dct), sort_dicts=False) + "\n)" 191 | assert str(dct) == s 192 | 193 | 194 | # if __name__ == '__main__': 195 | # 196 | # for ref, obj in locals().copy().items(): 197 | # if ref.startswith("test_"): 198 | # print("running: ", ref) 199 | # obj() 200 | -------------------------------------------------------------------------------- /tests/test_cme_agriculture_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | 4 | from pandas_market_calendars.calendars.cme import CMEAgricultureExchangeCalendar 5 | 6 | 7 | def test_time_zone(): 8 | assert CMEAgricultureExchangeCalendar().tz == ZoneInfo("America/Chicago") 9 | assert CMEAgricultureExchangeCalendar().name == "CME_Agriculture" 10 | 11 | 12 | def test_2020_holidays(): 13 | # martin luthur king: 2020-01-20 14 | # president's day: 2020-02-17 15 | # good friday: 2020-04-10 16 | # memorial day: 2020-05-25 17 | # independence day: 2020-04-02 and 2020-04-03 18 | # labor day: 2020-09-07 19 | # thanksgiving: 2020-11-25, 2020-11-26 20 | # christmas (observed): 2020-12-25, 2020-12-27 21 | # new years (observed): 2021-01-01 22 | # 23 | # These dates should be excluded, but are still in the calendar: 24 | # - 2020-04-02 25 | # - 2020-04-03 26 | # - 2020-11-25 27 | cme = CMEAgricultureExchangeCalendar() 28 | good_dates = cme.valid_days("2020-01-01", "2021-01-10") 29 | for date in [ 30 | "2020-01-20", 31 | "2020-02-17", 32 | "2020-04-10", 33 | "2020-05-25", 34 | "2020-09-07", 35 | "2020-11-26", 36 | "2020-12-25", 37 | "2020-12-27", 38 | "2021-01-01", 39 | ]: 40 | assert pd.Timestamp(date, tz="UTC") not in good_dates 41 | 42 | 43 | def test_dec_jan(): 44 | cme = CMEAgricultureExchangeCalendar() 45 | schedule = cme.schedule("2020-12-30", "2021-01-10") 46 | 47 | assert schedule["market_open"].iloc[0] == pd.Timestamp( 48 | "2020-12-29 23:01:00", tz="UTC" 49 | ) 50 | assert schedule["market_close"].iloc[6] == pd.Timestamp( 51 | "2021-01-08 23:00:00", tz="UTC" 52 | ) 53 | -------------------------------------------------------------------------------- /tests/test_cme_bond_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | 4 | from pandas_market_calendars.calendars.cme import CMEBondExchangeCalendar 5 | 6 | 7 | def test_time_zone(): 8 | assert CMEBondExchangeCalendar().tz == ZoneInfo("America/Chicago") 9 | assert CMEBondExchangeCalendar().name == "CME_Bond" 10 | 11 | 12 | def test_sunday_opens(): 13 | cme = CMEBondExchangeCalendar() 14 | schedule = cme.schedule("2020-01-01", "2020-01-31", tz="America/Chicago") 15 | assert ( 16 | pd.Timestamp("2020-01-12 17:00:00", tz="America/Chicago") 17 | == schedule.loc["2020-01-13", "market_open"] 18 | ) 19 | 20 | 21 | def test_2020_full_holidays(): 22 | # good friday: 2020-04-10 23 | # new years (observed): 2016-01-01 24 | # christmas (observed): 2020-12-25 25 | cme = CMEBondExchangeCalendar() 26 | good_dates = cme.valid_days("2020-01-01", "2020-12-31") 27 | for date in ["2020-04-10", "2020-01-01", "2020-12-25"]: 28 | assert pd.Timestamp(date, tz="UTC") not in good_dates 29 | 30 | 31 | def test_2020_noon_holidays(): 32 | # MLK: 2020-01-20 33 | # Presidents Day: 2020-02-17 34 | # Memorial Day: 2020-05-25 35 | # Labor Day: 2020-09-07 36 | # Thanksgiving: 2020-11-26 37 | cme = CMEBondExchangeCalendar() 38 | schedule = cme.schedule("2020-01-01", "2020-12-31") 39 | for date in ["2020-01-20", "2020-02-17", "2020-05-25", "2020-09-07", "2020-11-26"]: 40 | assert schedule.loc[date, "market_close"] == pd.Timestamp(date, tz="America/Chicago").replace( 41 | hour=12 42 | ).tz_convert("UTC") 43 | 44 | 45 | def test_2020_noon_15_holidays(): 46 | # Black Friday: 2020-11-27 47 | # Christmas Eve: 2020-12-24 48 | cme = CMEBondExchangeCalendar() 49 | schedule = cme.schedule("2020-11-27", "2020-12-24") 50 | for date in ["2020-11-27", "2020-12-24"]: 51 | assert schedule.loc[date, "market_close"] == pd.Timestamp(date, tz="America/Chicago").replace( 52 | hour=12, minute=15 53 | ).tz_convert("UTC") 54 | 55 | 56 | def test_good_fridays(): 57 | cme = CMEBondExchangeCalendar() 58 | schedule = cme.schedule("2020-01-01", "2021-12-31") 59 | assert pd.Timestamp("2020-04-10") not in schedule.index 60 | 61 | # Good Friday when it is the first friday of the month, open with early close 62 | assert pd.Timestamp("2021-04-02") in schedule.index 63 | assert schedule.loc[pd.Timestamp("2021-04-02"), "market_close"] == pd.Timestamp( 64 | "2021-04-02", tz="America/Chicago" 65 | ).replace(hour=10, minute=00).tz_convert("UTC") 66 | -------------------------------------------------------------------------------- /tests/test_cme_equity_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | 4 | from pandas_market_calendars.calendars.cme import CMEEquityExchangeCalendar 5 | 6 | 7 | def test_time_zone(): 8 | assert CMEEquityExchangeCalendar().tz == ZoneInfo("America/Chicago") 9 | assert CMEEquityExchangeCalendar().name == "CME_Equity" 10 | 11 | 12 | def test_sunday_opens(): 13 | cme = CMEEquityExchangeCalendar() 14 | schedule = cme.schedule("2020-01-01", "2020-01-31", tz="America/New_York") 15 | assert ( 16 | pd.Timestamp("2020-01-12 18:00:00", tz="America/New_York") 17 | == schedule.loc["2020-01-13", "market_open"] 18 | ) 19 | 20 | 21 | def test_2016_holidays(): 22 | # good friday: 2016-03-25 23 | # christmas (observed): 2016-12-26 24 | # new years (observed): 2016-01-02 25 | cme = CMEEquityExchangeCalendar() 26 | good_dates = cme.valid_days("2016-01-01", "2016-12-31") 27 | for date in ["2016-03-25", "2016-12-26", "2016-01-02"]: 28 | assert pd.Timestamp(date, tz="UTC") not in good_dates 29 | 30 | 31 | def test_2016_early_closes(): 32 | # mlk day: 2016-01-18 33 | # presidents: 2016-02-15 34 | # mem day: 2016-05-30 35 | # july 4: 2016-07-04 36 | # labor day: 2016-09-05 37 | # thanksgiving: 2016-11-24 38 | 39 | cme = CMEEquityExchangeCalendar() 40 | schedule = cme.schedule("2016-01-01", "2016-12-31") 41 | early_closes = cme.early_closes(schedule).index 42 | 43 | for date in [ 44 | "2016-01-18", 45 | "2016-02-15", 46 | "2016-05-30", 47 | "2016-07-04", 48 | "2016-09-05", 49 | "2016-11-24", 50 | ]: 51 | dt = pd.Timestamp(date) 52 | assert dt in early_closes 53 | 54 | market_close = schedule.loc[dt].market_close 55 | assert market_close.tz_convert(cme.tz).hour == 12 56 | 57 | 58 | def test_dec_jan(): 59 | cme = CMEEquityExchangeCalendar() 60 | schedule = cme.schedule("2016-12-30", "2017-01-10") 61 | 62 | assert schedule["market_open"].iloc[0] == pd.Timestamp("2016-12-29 23:00:00", tz="UTC") 63 | assert schedule["market_close"].iloc[6] == pd.Timestamp("2017-01-10 22:00:00", tz="UTC") 64 | -------------------------------------------------------------------------------- /tests/test_eurex_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | 4 | from pandas_market_calendars.calendars.eurex import EUREXExchangeCalendar 5 | 6 | 7 | def test_time_zone(): 8 | assert EUREXExchangeCalendar().tz == ZoneInfo("Europe/Berlin") 9 | assert EUREXExchangeCalendar().name == "EUREX" 10 | 11 | 12 | def test_2016_holidays(): 13 | # good friday: 2016-03-25 14 | # May 1st: on a weekend, not rolled forward 15 | # christmas: on a weekend, not rolled forward 16 | # boxing day: 2016-12-26 17 | # new years (observed): 2016-01-01 18 | eurex = EUREXExchangeCalendar() 19 | good_dates = eurex.valid_days("2016-01-01", "2016-12-31") 20 | for date in ["2016-03-25", "2016-01-01", "2016-12-26"]: 21 | assert pd.Timestamp(date, tz="UTC") not in good_dates 22 | for date in ["2016-05-02"]: 23 | assert pd.Timestamp(date, tz="UTC") in good_dates 24 | 25 | 26 | def test_2017_holidays(): 27 | # good friday: 2017-04-14 28 | # May 1st: 2017-05-01 29 | # christmas (observed): 2017-12-25 30 | # new years (observed): on a weekend, not rolled forward 31 | eurex = EUREXExchangeCalendar() 32 | good_dates = eurex.valid_days("2017-01-01", "2017-12-31") 33 | for date in ["2016-04-14", "2017-05-01", "2017-12-25"]: 34 | assert pd.Timestamp(date, tz="UTC") not in good_dates 35 | for date in ["2017-01-02"]: 36 | assert pd.Timestamp(date, tz="UTC") in good_dates 37 | -------------------------------------------------------------------------------- /tests/test_eurex_fixed_income_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | 4 | from pandas_market_calendars.calendars.eurex_fixed_income import ( 5 | EUREXFixedIncomeCalendar, 6 | ) 7 | 8 | 9 | def test_time_zone(): 10 | assert EUREXFixedIncomeCalendar().tz == ZoneInfo("Europe/Berlin") 11 | assert EUREXFixedIncomeCalendar().name == "EUREX_Bond" 12 | 13 | 14 | def _test_year_holiday(year, bad_dates): 15 | eurex = EUREXFixedIncomeCalendar() 16 | good_dates = eurex.valid_days(f"{year}-01-01", f"{year}-12-31") 17 | 18 | # Make sure holiday dates aren't in the schedule 19 | for date in bad_dates: 20 | assert pd.Timestamp(date, tz="UTC") not in good_dates 21 | 22 | # Make sure all other weekdays are in the schedule 23 | expected_good_dates = [ 24 | d.strftime("%Y-%m-%d") 25 | for d in pd.date_range(f"{year}-01-01", f"{year}-12-31", freq="D") 26 | if d.weekday() < 5 and d.strftime("%Y-%m-%d") not in bad_dates 27 | ] 28 | for date in expected_good_dates: 29 | assert pd.Timestamp(date, tz="UTC") in good_dates 30 | 31 | 32 | def test_2017_holidays(): 33 | """ 34 | Eurex is closed for trading and clearing (exercise, settlement and cash) 35 | in all derivatives: 14 April, 17 April, 1 May, 25 December, 26 December 36 | """ 37 | bad_dates = ["2017-04-14", "2017-04-17", "2017-05-01", "2017-12-25", "2017-12-26"] 38 | _test_year_holiday(2017, bad_dates) 39 | 40 | 41 | def test_2018_holidays(): 42 | """ 43 | Eurex is closed for trading and clearing (exercise, settlement and cash) 44 | in all derivatives: 1 January, 30 March, 2 April, 1 May, 25 December, 26 December 45 | Eurex is closed for trading in all derivatives: 24 December, 31 December 46 | """ 47 | bad_dates = [ 48 | "2018-01-01", 49 | "2018-03-30", 50 | "2018-04-02", 51 | "2018-05-01", 52 | "2018-12-24", 53 | "2018-12-25", 54 | "2018-12-26", 55 | "2018-12-31", 56 | ] 57 | _test_year_holiday(2018, bad_dates) 58 | 59 | 60 | def test_2019_holidays(): 61 | """ 62 | Eurex is closed for trading and clearing (exercise, settlement and cash) 63 | in all derivatives: 1 January, 19 April, 22 April, 1 May, 25 December, 26 December 64 | Eurex is closed for trading in all derivatives: 24 December, 31 December 65 | """ 66 | bad_dates = [ 67 | "2019-01-01", 68 | "2019-04-19", 69 | "2019-04-22", 70 | "2019-05-01", 71 | "2019-12-24", 72 | "2019-12-25", 73 | "2019-12-26", 74 | "2019-12-31", 75 | ] 76 | _test_year_holiday(2019, bad_dates) 77 | 78 | 79 | def test_2020_holidays(): 80 | """ 81 | Eurex is closed for trading and clearing (exercise, settlement and cash) 82 | in all derivatives: 1 January, 10 April, 13 April, 1 May, 25 December 83 | Eurex is closed for trading in all derivatives: 24 December, 31 December 84 | """ 85 | bad_dates = [ 86 | "2020-01-01", 87 | "2020-04-10", 88 | "2020-04-13", 89 | "2020-05-01", 90 | "2020-12-24", 91 | "2020-12-25", 92 | "2020-12-31", 93 | ] 94 | _test_year_holiday(2020, bad_dates) 95 | 96 | 97 | def test_2021_holidays(): 98 | """ 99 | Eurex is closed for trading and clearing (exercise, settlement and cash) 100 | in all derivatives: 1 January, 2 April, 5 April 101 | Eurex is closed for trading in all derivatives: 24 December, 31 December 102 | """ 103 | bad_dates = [ 104 | "2021-01-01", 105 | "2021-04-02", 106 | "2021-04-05", 107 | "2021-05-01", 108 | "2021-12-24", 109 | "2021-12-31", 110 | ] 111 | _test_year_holiday(2021, bad_dates) 112 | 113 | 114 | def test_2022_holidays(): 115 | """ 116 | Eurex is closed for trading and clearing (exercise, settlement and cash) 117 | in all derivatives: 15 April, 18 April, 26 December 118 | """ 119 | bad_dates = ["2022-04-15", "2022-04-18", "2022-12-26"] 120 | _test_year_holiday(2022, bad_dates) 121 | 122 | 123 | def test_2023_holidays(): 124 | """ 125 | Eurex is closed for trading and clearing (exercise, settlement and cash) 126 | in all derivatives: 7 April, 10 April, 1 May, 25 December, 26 December 127 | """ 128 | bad_dates = ["2023-04-07", "2023-04-10", "2023-05-01", "2023-12-25", "2023-12-26"] 129 | _test_year_holiday(2023, bad_dates) 130 | 131 | 132 | def test_2024_holidays(): 133 | bad_dates = [ 134 | "2024-01-01", 135 | "2024-03-29", 136 | "2024-04-01", 137 | "2024-05-01", 138 | "2024-12-24", 139 | "2024-12-25", 140 | "2024-12-26", 141 | "2024-12-31", 142 | ] 143 | _test_year_holiday(2024, bad_dates) 144 | -------------------------------------------------------------------------------- /tests/test_exchange_calendar_cme_globex_energy_and_metals.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | from pandas.testing import assert_index_equal 4 | 5 | from pandas_market_calendars.calendars.cme_globex_energy_and_metals import ( 6 | CMEGlobexEnergyAndMetalsExchangeCalendar, 7 | ) 8 | 9 | cal = CMEGlobexEnergyAndMetalsExchangeCalendar() 10 | 11 | 12 | def test_time_zone(): 13 | assert cal.tz == ZoneInfo("America/Chicago") 14 | assert cal.name == "CMEGlobex_EnergyAndMetals" 15 | 16 | 17 | def test_open_time_tz(): 18 | assert cal.open_time.tzinfo == cal.tz 19 | 20 | 21 | def test_close_time_tz(): 22 | assert cal.close_time.tzinfo == cal.tz 23 | 24 | 25 | def test_weekmask(): 26 | assert cal.weekmask == "Mon Tue Wed Thu Fri" 27 | 28 | 29 | def _test_holidays(holidays, start, end): 30 | df = pd.DataFrame(cal.holidays().holidays, columns=["holidays"]) 31 | mask = (df["holidays"] >= start) & (df["holidays"] <= end) 32 | df = df[mask] 33 | assert len(holidays) == len(df) 34 | df = df.set_index(["holidays"]) 35 | df.index = df.index.tz_localize("UTC") 36 | assert_index_equal(pd.DatetimeIndex(holidays), df.index, check_names=False) 37 | valid_days = cal.valid_days(start, end) 38 | for h in holidays: 39 | assert h not in valid_days 40 | 41 | 42 | def _test_no_special_opens(start, end): 43 | assert len(cal.late_opens(cal.schedule(start, end))) == 0 44 | 45 | 46 | def _test_no_special_closes(start, end): 47 | assert len(cal.early_closes(cal.schedule(start, end))) == 0 48 | 49 | 50 | def _test_no_special_opens_closes(start, end): 51 | _test_no_special_opens(start, end) 52 | _test_no_special_closes(start, end) 53 | 54 | 55 | def _test_verify_late_open_time(schedule, timestamp): 56 | date = pd.Timestamp(pd.Timestamp(timestamp).tz_convert("UTC").date()) 57 | if date in schedule.index: 58 | return schedule.at[date, "market_open"] == timestamp 59 | else: 60 | return False 61 | 62 | 63 | def _test_has_late_opens(late_opens, start, end): 64 | schedule = cal.schedule(start, end) 65 | expected = cal.late_opens(schedule) 66 | assert len(expected) == len(late_opens) 67 | for ts in late_opens: 68 | assert _test_verify_late_open_time(schedule, ts) is True 69 | 70 | 71 | def _test_verify_early_close_time(schedule, timestamp): 72 | date = pd.Timestamp(pd.Timestamp(timestamp).tz_convert("UTC").date()) 73 | if date in schedule.index: 74 | return schedule.at[date, "market_close"] == timestamp 75 | else: 76 | return False 77 | 78 | 79 | def _test_has_early_closes(early_closes, start, end): 80 | schedule = cal.schedule(start, end) 81 | expected = cal.early_closes(schedule) 82 | assert len(expected) == len(early_closes) 83 | for ts in early_closes: 84 | assert _test_verify_early_close_time(schedule, ts) is True 85 | 86 | 87 | ######################################################################### 88 | # YEARLY TESTS BEGIN 89 | ######################################################################### 90 | def test_2022(): 91 | start = "2022-01-01" 92 | end = "2022-12-31" 93 | holidays = [ 94 | pd.Timestamp("2022-04-15", tz="UTC"), # Good Friday 95 | pd.Timestamp("2022-12-26", tz="UTC"), # Christmas 96 | ] 97 | _test_holidays(holidays, start, end) 98 | _test_no_special_opens(start, end) 99 | 100 | early_closes = [ 101 | pd.Timestamp("2022-01-17 1:30PM", tz="America/Chicago"), # MLK 102 | pd.Timestamp("2022-02-21 1:30PM", tz="America/Chicago"), # Presidents Day 103 | pd.Timestamp("2022-05-30 1:30PM", tz="America/Chicago"), # Memorial Day 104 | pd.Timestamp("2022-06-20 1:30PM", tz="America/Chicago"), # Juneteenth 105 | pd.Timestamp("2022-07-04 1:30PM", tz="America/Chicago"), # Independence Day 106 | pd.Timestamp("2022-09-05 12:00PM", tz="America/Chicago"), # Labor Day 107 | pd.Timestamp("2022-11-24 1:30PM", tz="America/Chicago"), # US Thanksgiving 108 | pd.Timestamp( 109 | "2022-11-25 12:45PM", tz="America/Chicago" 110 | ), # Friday after US Thanksgiving 111 | ] 112 | _test_has_early_closes(early_closes, start, end) 113 | 114 | 115 | def test_2021(): 116 | start = "2021-01-01" 117 | end = "2021-12-31" 118 | holidays = [ 119 | pd.Timestamp("2021-01-01", tz="UTC"), # New Years 120 | pd.Timestamp("2021-04-02", tz="UTC"), # Good Friday 121 | pd.Timestamp("2021-12-24", tz="UTC"), # Christmas 122 | ] 123 | _test_holidays(holidays, start, end) 124 | _test_no_special_opens(start, end) 125 | 126 | early_closes = [ 127 | pd.Timestamp("2021-01-18 12:00PM", tz="America/Chicago"), # MLK 128 | pd.Timestamp("2021-02-15 12:00PM", tz="America/Chicago"), # Presidents Day 129 | pd.Timestamp("2021-05-31 12:00PM", tz="America/Chicago"), # Memorial Day 130 | pd.Timestamp("2021-07-05 12:00PM", tz="America/Chicago"), # Independence Day 131 | pd.Timestamp("2021-09-06 12:00PM", tz="America/Chicago"), # Labor Day 132 | pd.Timestamp("2021-11-25 12:00PM", tz="America/Chicago"), # US Thanksgiving 133 | pd.Timestamp("2021-11-26 12:45PM", tz="America/Chicago"), # Friday after US Thanksgiving 134 | ] 135 | _test_has_early_closes(early_closes, start, end) 136 | 137 | 138 | def test_2020(): 139 | start = "2020-01-01" 140 | end = "2020-12-31" 141 | holidays = [ 142 | pd.Timestamp("2020-01-01", tz="UTC"), # New Years 143 | pd.Timestamp("2020-04-10", tz="UTC"), # Good Friday 144 | pd.Timestamp("2020-12-25", tz="UTC"), # Christmas 145 | ] 146 | _test_holidays(holidays, start, end) 147 | _test_no_special_opens(start, end) 148 | 149 | early_closes = [ 150 | pd.Timestamp("2020-01-20 12:00PM", tz="America/Chicago"), # MLK 151 | pd.Timestamp("2020-02-17 12:00PM", tz="America/Chicago"), # Presidents Day 152 | pd.Timestamp("2020-05-25 12:00PM", tz="America/Chicago"), # Memorial Day 153 | pd.Timestamp("2020-07-03 12:00PM", tz="America/Chicago"), # Independence Day 154 | pd.Timestamp("2020-09-07 12:00PM", tz="America/Chicago"), # Labor Day 155 | pd.Timestamp("2020-11-26 12:00PM", tz="America/Chicago"), # US Thanksgiving 156 | pd.Timestamp("2020-11-27 12:45PM", tz="America/Chicago"), # Friday after US Thanksgiving 157 | ] 158 | _test_has_early_closes(early_closes, start, end) 159 | -------------------------------------------------------------------------------- /tests/test_exchange_calendar_cme_globex_fx.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | from zoneinfo import ZoneInfo 4 | from pandas.tseries.offsets import Day, Hour, Minute 5 | 6 | from pandas_market_calendars.calendars.cme_globex_fx import CMEGlobexFXExchangeCalendar 7 | 8 | TZ = "America/Chicago" 9 | 10 | 11 | def test_time_zone(): 12 | assert CMEGlobexFXExchangeCalendar().tz == ZoneInfo(TZ) 13 | assert CMEGlobexFXExchangeCalendar().name == "CMEGlobex_FX" 14 | 15 | 16 | def test_sunday_opens(): 17 | cme = CMEGlobexFXExchangeCalendar() 18 | schedule = cme.schedule("2020-01-01", "2020-01-31") 19 | assert ( 20 | pd.Timestamp("2020-01-12 17:00:00", tz=TZ) 21 | == schedule.loc["2020-01-13", "market_open"] 22 | ) 23 | 24 | 25 | @pytest.mark.parametrize( 26 | "day_status", 27 | [ 28 | # 2020 29 | # 2020 Martin Luther King Day (20th = Monday) 30 | ("2020-01-17", "open"), 31 | ("2020-01-20", "1200"), 32 | ("2020-01-21", "open"), 33 | # 2020 Presidents Day (17th = Monday) 34 | ("2020-02-14", "open"), 35 | ("2020-02-17", "1200"), 36 | ("2020-02-18", "open"), 37 | # 2020 Good Friday (10th = Friday) 38 | ("2020-04-09", "open"), 39 | ("2020-04-10", "closed"), 40 | ("2020-04-13", "open"), 41 | # 2020 Memorial Day (May 25 = Monday) 42 | ("2020-05-22", "open"), 43 | ("2020-05-25", "1200"), 44 | ("2020-05-26", "open"), 45 | # 2020 Independence Day (4th = Saturday) 46 | ("2020-07-02", "open"), 47 | ("2020-07-03", "1200"), 48 | ("2020-07-06", "open"), 49 | # 2020 Labor Day (4th = Monday) 50 | ("2020-09-04", "open"), 51 | ("2020-09-07", "1200"), 52 | ("2020-09-08", "open"), 53 | # 2020 Thanksgiving (26th = Thursday) 54 | ("2020-11-25", "open"), 55 | ("2020-11-26", "1200"), 56 | ("2020-11-27", "1215"), 57 | ("2020-11-30", "open"), 58 | # 2020 Christmas (25th = Friday) 59 | ("2020-12-24", "1215"), 60 | ("2020-12-25", "closed"), 61 | ("2020-12-28", "open"), 62 | ("2020-12-29", "open"), 63 | # 2020/21 New Year's (Dec 31 = Thur) 64 | ("2020-12-31", "open"), 65 | ("2021-01-01", "closed"), 66 | ("2022-01-04", "open"), 67 | # 2021 68 | # 2021 Martin Luther King Day (18th = Monday) 69 | ("2021-01-15", "open"), 70 | ("2021-01-18", "1200"), 71 | ("2021-01-19", "open"), 72 | # 2021 Presidents Day (15th = Monday) 73 | ("2021-02-12", "open"), 74 | ("2021-02-15", "1200"), 75 | ("2021-02-16", "open"), 76 | # 2021 Good Friday (2nd = Friday) 77 | ("2021-04-01", "open"), 78 | ("2021-04-02", "1015"), 79 | ("2021-04-05", "open"), 80 | # 2021 Memorial Day (May 31 = Monday) 81 | ("2021-05-28", "open"), 82 | ("2021-05-31", "1200"), 83 | ("2021-06-01", "open"), 84 | # 2021 Independence Day (4th = Sunday) 85 | ("2021-07-02", "open"), 86 | ("2021-07-05", "1200"), 87 | ("2021-07-06", "open"), 88 | # 2021 Labor Day (6th = Monday) 89 | ("2021-09-03", "open"), 90 | ("2021-09-06", "1200"), 91 | ("2021-09-07", "open"), 92 | # 2021 Thanksgiving (25th = Thursday) 93 | ("2021-11-24", "open"), 94 | ("2021-11-25", "1200"), 95 | ("2021-11-26", "1215"), 96 | # 2021 Christmas (25th = Saturday) 97 | ("2021-12-23", "open"), 98 | ("2021-12-24", "closed"), 99 | ("2021-12-27", "open"), 100 | # 2021/22 New Year's (Dec 31 = Friday) (unusually this period was fully open) 101 | ("2021-12-31", "open"), 102 | ("2022-01-03", "open"), 103 | ("2022-01-03", "open"), 104 | # 2022 105 | # 2022 Martin Luther King Day (17th = Monday) 106 | ("2022-01-14", "open"), 107 | ("2022-01-17", "open"), 108 | ("2022-01-18", "open"), 109 | # 2022 President's Day (21st = Monday) 110 | ("2022-02-18", "open"), 111 | ("2022-02-21", "open"), 112 | ("2022-02-22", "open"), 113 | # 2022 Good Friday (15 = Friday) 114 | ("2022-04-14", "open"), 115 | ("2022-04-15", "closed"), 116 | ("2022-04-18", "open"), 117 | # 2022 Memorial Day (30th = Monday) 118 | ("2022-05-27", "open"), 119 | ("2022-05-30", "open"), 120 | ("2022-05-31", "open"), 121 | # 2022 Juneteenth (20th = Monday) 122 | ("2022-06-17", "open"), 123 | ("2022-06-20", "open"), 124 | ("2022-06-21", "open"), 125 | # 2022 Independence Day (4th = Monday) 126 | ("2022-07-01", "open"), 127 | ("2022-07-04", "open"), 128 | ("2022-07-05", "open"), 129 | # 2022 Labor Day (5th = Monday) 130 | ("2022-09-02", "open"), 131 | ("2022-09-05", "open"), 132 | ("2022-09-06", "open"), 133 | # 2022 Thanksgiving (24th = Thursday) 134 | ("2022-11-23", "open"), 135 | ("2022-11-24", "open"), 136 | ("2022-11-25", "1215"), 137 | ("2022-11-28", "open"), 138 | # 2022 Christmas (25 = Sunday) 139 | ("2022-12-23", "open"), 140 | ("2022-12-26", "closed"), 141 | ("2022-12-27", "open"), 142 | # 2022/23 New Years (Jan 1 = Sunday) 143 | ("2022-12-30", "open"), 144 | ("2023-01-02", "closed"), 145 | ("2023-01-03", "open"), 146 | ("2023-04-07", "1015"), 147 | ], 148 | ids=lambda x: f"{x[0]} {x[1]}", 149 | ) 150 | def test_2020_through_2022_and_prior_holidays(day_status): 151 | day_str = day_status[0] 152 | day_ts = pd.Timestamp(day_str, tz=TZ) 153 | expected_status = day_status[1] 154 | 155 | under_test = CMEGlobexFXExchangeCalendar() 156 | schedule = under_test.schedule("2020-01-01", "2023-04-28", tz=TZ) 157 | 158 | if expected_status == "open": 159 | s = schedule.loc[day_str] 160 | assert s["market_open"] == day_ts + Day(-1) + Hour(17) + Minute(0) 161 | assert s["market_close"] == day_ts + Day(0) + Hour(16) + Minute(0) 162 | elif expected_status == "closed": 163 | assert day_ts.tz_localize(None) not in schedule.index 164 | else: 165 | s = schedule.loc[day_str] 166 | hour = int(expected_status[0:2]) 167 | minute = int(expected_status[2:4]) 168 | assert s["market_open"] == day_ts + Day(-1) + Hour(17) 169 | assert s["market_close"] == day_ts + Day(0) + Hour(hour) + Minute(minute) 170 | -------------------------------------------------------------------------------- /tests/test_exchange_calendar_cme_globex_grains.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | from pandas.testing import assert_index_equal 4 | 5 | from pandas_market_calendars.calendars.cme_globex_agriculture import ( 6 | CMEGlobexGrainsAndOilseedsExchangeCalendar, 7 | ) 8 | 9 | cal = CMEGlobexGrainsAndOilseedsExchangeCalendar() 10 | 11 | 12 | def test_time_zone(): 13 | assert cal.tz == ZoneInfo("America/Chicago") 14 | assert cal.name == "CMEGlobex_GrainsAndOilseeds" 15 | 16 | 17 | def test_x(): 18 | schedule = cal.schedule("2023-01-01", "2023-01-10", tz="America/New_York") 19 | 20 | good_dates = cal.valid_days("2023-01-01", "2023-12-31") 21 | 22 | assert all( 23 | d not in good_dates 24 | for d in {"2023-01-01", "2023-12-24", "2023-12-25", "2023-12-30", "2023-12-31"} 25 | ) 26 | 27 | assert all(d in good_dates for d in {"2023-01-03", "2023-01-05", "2023-12-26", "2023-12-27", "2023-12-28"}) 28 | -------------------------------------------------------------------------------- /tests/test_hkex_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pandas as pd 4 | from zoneinfo import ZoneInfo 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/test_iex_calendar.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | import numpy as np 4 | import pandas as pd 5 | from zoneinfo import ZoneInfo 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( 32 | start_date="2015-07-01", end_date="2017-07-10", start="pre", end="post" 33 | ) 34 | assert isinstance(schedule, pd.DataFrame) 35 | assert not schedule.empty 36 | 37 | 38 | def test_trading_days_before_operation(): 39 | trading_days = iex.valid_days(start_date="2000-01-01", end_date="2022-02-23") 40 | assert np.array([~(trading_days <= "2013-08-25")]).any() 41 | 42 | trading_days = iex.date_range_htf("1D", "2000-01-01", "2022-02-23") 43 | assert np.array([~(trading_days <= "2013-08-25")]).any() 44 | 45 | trading_days = iex.date_range_htf("1D", "2000-01-01", "2010-02-23") 46 | assert len(trading_days) == 0 47 | -------------------------------------------------------------------------------- /tests/test_lse_calendar.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | 3 | import pandas as pd 4 | from zoneinfo import ZoneInfo 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 = { 78 | i: {"closed": None, "open": None} for i in england_unique_hols_names 79 | } 80 | 81 | # One-off holiday additions and removals in England 82 | 83 | # VE-Day Anniversary 84 | # 50th Anniversary 85 | england_unique_hols["VE_50"]["closed"] = [pd.Timestamp("1995-05-08")] 86 | england_unique_hols["VE_50"]["open"] = [pd.Timestamp("1995-05-01")] # Early May bank holiday removed 87 | # 75th Anniversary 88 | england_unique_hols["VE_75"]["closed"] = [pd.Timestamp("2020-05-08")] 89 | england_unique_hols["VE_75"]["open"] = [pd.Timestamp("2020-05-04")] # Early May bank holiday removed 90 | 91 | # Queen Elizabeth II Jubilees 92 | # Silver Jubilee 93 | england_unique_hols["QEII_Jubilee_25"]["closed"] = [pd.Timestamp("1977-06-07")] 94 | # Golden Jubilee 95 | england_unique_hols["QEII_Jubilee_50"]["closed"] = [ 96 | pd.Timestamp("2002-06-03"), 97 | pd.Timestamp("2002-06-04"), 98 | ] 99 | england_unique_hols["QEII_Jubilee_50"]["open"] = [pd.Timestamp("2002-05-27")] # Spring bank holiday removed 100 | # Diamond Jubilee 101 | england_unique_hols["QEII_Jubilee_60"]["closed"] = [ 102 | pd.Timestamp("2012-06-04"), 103 | pd.Timestamp("2012-06-05"), 104 | ] 105 | england_unique_hols["QEII_Jubilee_60"]["open"] = [pd.Timestamp("2012-05-28")] # Spring bank holiday removed 106 | # Platinum Jubilee 107 | england_unique_hols["QEII_Jubilee_60"]["closed"] = [ 108 | pd.Timestamp("2022-06-02"), 109 | pd.Timestamp("2022-06-03"), 110 | ] 111 | england_unique_hols["QEII_Jubilee_60"]["open"] = [pd.Timestamp("2022-05-31")] # Spring bank holiday removed 112 | 113 | # State Funeral of Queen Elizabeth II 114 | england_unique_hols["QEII_StateFuneral"]["closed"] = [pd.Timestamp("2022-09-19")] 115 | 116 | # Royal Weddings 117 | # Wedding Day of Princess Anne and Mark Phillips 118 | england_unique_hols["Royal_Wedding_Anne_1973"]["closed"] = [pd.Timestamp("1973-11-14")] 119 | # Wedding Day of Prince Charles and Diana Spencer 120 | england_unique_hols["Royal_Wedding_Charles_1981"]["closed"] = [pd.Timestamp("1981-07-29")] 121 | # Wedding Day of Prince William and Catherine Middleton 122 | england_unique_hols["Royal_Wedding_William_2011"]["closed"] = [pd.Timestamp("2011-04-29")] 123 | 124 | # Coronation of King Charles III 125 | england_unique_hols["KCIII_Coronation"]["closed"] = [pd.Timestamp("2023-05-08")] 126 | 127 | # Miscellaneous 128 | # Eve of 3rd Millennium A.D. 129 | england_unique_hols["3rd_Millennium_Eve"]["closed"] = [pd.Timestamp("1999-12-31")] 130 | 131 | # Test of closed dates 132 | lse = LSEExchangeCalendar() 133 | # get all the closed dates 134 | closed_days = [england_unique_hols[k].get("closed") for k in england_unique_hols] 135 | good_dates = lse.valid_days("1990-01-01", "2022-12-31") 136 | for date in chain.from_iterable(closed_days): 137 | assert pd.Timestamp(date, tz="UTC") not in good_dates 138 | 139 | # Test of open dates 140 | open_days = [england_unique_hols[k].get("open") for k in england_unique_hols] 141 | open_days = [i for i in open_days if i] 142 | good_dates = lse.valid_days("1990-01-01", "2022-12-31") 143 | for date in chain.from_iterable(open_days): 144 | assert pd.Timestamp(date, tz="UTC") in good_dates 145 | -------------------------------------------------------------------------------- /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 | import pandas as pd 11 | import pytest 12 | from zoneinfo import ZoneInfo 13 | 14 | from pandas_market_calendars.calendars.ose import OSEExchangeCalendar 15 | 16 | TIMEZONE = ZoneInfo("Europe/Oslo") 17 | 18 | 19 | def test_time_zone(): 20 | assert OSEExchangeCalendar().tz == ZoneInfo("Europe/Oslo") 21 | 22 | 23 | def test_name(): 24 | assert OSEExchangeCalendar().name == "OSE" 25 | 26 | 27 | def test_open_time_tz(): 28 | ose = OSEExchangeCalendar() 29 | assert ose.open_time.tzinfo == ose.tz 30 | 31 | 32 | def test_close_time_tz(): 33 | ose = OSEExchangeCalendar() 34 | assert ose.close_time.tzinfo == ose.tz 35 | 36 | 37 | def test_2017_calendar(): 38 | ose = OSEExchangeCalendar() 39 | ose_schedule = ose.schedule( 40 | start_date=pd.Timestamp("2017-04-11", tz=TIMEZONE), 41 | end_date=pd.Timestamp("2017-04-13", tz=TIMEZONE), 42 | ) 43 | 44 | regular_holidays_2017 = [ 45 | pd.Timestamp("2017-04-13", tz=TIMEZONE), 46 | pd.Timestamp("2017-04-14", tz=TIMEZONE), 47 | pd.Timestamp("2017-04-17", tz=TIMEZONE), 48 | pd.Timestamp("2017-05-01", tz=TIMEZONE), 49 | pd.Timestamp("2017-05-17", tz=TIMEZONE), 50 | pd.Timestamp("2017-05-25", tz=TIMEZONE), 51 | pd.Timestamp("2017-06-05", tz=TIMEZONE), 52 | pd.Timestamp("2017-12-25", tz=TIMEZONE), 53 | pd.Timestamp("2017-12-26", tz=TIMEZONE), 54 | ] 55 | 56 | half_trading_days_2017 = [pd.Timestamp("2017-04-12", tz=TIMEZONE)] 57 | 58 | valid_market_dates = ose.valid_days("2017-01-01", "2017-12-31", tz=TIMEZONE) 59 | 60 | for closed_market_date in regular_holidays_2017: 61 | assert closed_market_date not in valid_market_dates 62 | 63 | for half_trading_day in half_trading_days_2017: 64 | assert half_trading_day in valid_market_dates 65 | 66 | assert ose.open_at_time( 67 | schedule=ose_schedule, timestamp=pd.Timestamp("2017-04-12 12PM", tz=TIMEZONE) 68 | ) 69 | with pytest.raises(ValueError): 70 | ose.open_at_time(schedule=ose_schedule, timestamp=pd.Timestamp("2017-04-12 2PM", tz=TIMEZONE)) 71 | 72 | 73 | def test_2018_calendar(): 74 | ose = OSEExchangeCalendar() 75 | ose_schedule = ose.schedule( 76 | start_date=pd.Timestamp("2018-03-27", tz=TIMEZONE), 77 | end_date=pd.Timestamp("2018-03-29", tz=TIMEZONE), 78 | ) 79 | 80 | regular_holidays_2018 = [ 81 | pd.Timestamp("2018-01-01", tz=TIMEZONE), 82 | pd.Timestamp("2018-03-29", tz=TIMEZONE), 83 | pd.Timestamp("2018-03-30", tz=TIMEZONE), 84 | pd.Timestamp("2018-05-01", tz=TIMEZONE), 85 | pd.Timestamp("2018-05-10", tz=TIMEZONE), 86 | pd.Timestamp("2018-05-17", tz=TIMEZONE), 87 | pd.Timestamp("2018-05-21", tz=TIMEZONE), 88 | pd.Timestamp("2018-12-24", tz=TIMEZONE), 89 | pd.Timestamp("2018-12-25", tz=TIMEZONE), 90 | pd.Timestamp("2018-12-26", tz=TIMEZONE), 91 | pd.Timestamp("2018-12-31", tz=TIMEZONE), 92 | ] 93 | 94 | half_trading_days_2018 = [pd.Timestamp("2018-03-28", tz=TIMEZONE)] 95 | 96 | valid_market_dates = ose.valid_days("2018-01-01", "2018-12-31", tz=TIMEZONE) 97 | 98 | for closed_market_date in regular_holidays_2018: 99 | assert closed_market_date not in valid_market_dates 100 | 101 | for half_trading_day in half_trading_days_2018: 102 | assert half_trading_day in valid_market_dates 103 | 104 | assert ose.open_at_time(schedule=ose_schedule, timestamp=pd.Timestamp("2018-03-28 12PM", tz=TIMEZONE)) 105 | with pytest.raises(ValueError): 106 | ose.open_at_time( 107 | schedule=ose_schedule, 108 | timestamp=pd.Timestamp("2018-03-28 1:10PM", tz=TIMEZONE), 109 | ) 110 | 111 | 112 | def test_2019_calendar(): 113 | ose = OSEExchangeCalendar() 114 | ose_schedule = ose.schedule( 115 | start_date=pd.Timestamp("2019-04-16", tz=TIMEZONE), 116 | end_date=pd.Timestamp("2019-04-18", tz=TIMEZONE), 117 | ) 118 | 119 | regular_holidays_2019 = [ 120 | pd.Timestamp("2019-01-01", tz=TIMEZONE), 121 | pd.Timestamp("2019-04-18", tz=TIMEZONE), 122 | pd.Timestamp("2019-04-19", tz=TIMEZONE), 123 | pd.Timestamp("2019-04-22", tz=TIMEZONE), 124 | pd.Timestamp("2019-05-01", tz=TIMEZONE), 125 | pd.Timestamp("2019-05-17", tz=TIMEZONE), 126 | pd.Timestamp("2019-05-30", tz=TIMEZONE), 127 | pd.Timestamp("2019-06-10", tz=TIMEZONE), 128 | pd.Timestamp("2019-12-24", tz=TIMEZONE), 129 | pd.Timestamp("2019-12-25", tz=TIMEZONE), 130 | pd.Timestamp("2019-12-26", tz=TIMEZONE), 131 | pd.Timestamp("2019-12-31", tz=TIMEZONE), 132 | ] 133 | 134 | half_trading_days_2019 = [pd.Timestamp("2019-04-17", tz=TIMEZONE)] 135 | 136 | valid_market_dates = ose.valid_days("2019-01-01", "2019-12-31", tz=TIMEZONE) 137 | 138 | for closed_market_date in regular_holidays_2019: 139 | assert closed_market_date not in valid_market_dates 140 | 141 | for half_trading_day in half_trading_days_2019: 142 | assert half_trading_day in valid_market_dates 143 | 144 | assert ose.open_at_time(schedule=ose_schedule, timestamp=pd.Timestamp("2019-04-17 12PM", tz=TIMEZONE)) 145 | with pytest.raises(ValueError): 146 | ose.open_at_time( 147 | schedule=ose_schedule, 148 | timestamp=pd.Timestamp("2019-04-17 1:10PM", tz=TIMEZONE), 149 | ) 150 | -------------------------------------------------------------------------------- /tests/test_six_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | 4 | from pandas_market_calendars.calendars.six import SIXExchangeCalendar 5 | 6 | 7 | def test_time_zone(): 8 | assert SIXExchangeCalendar().tz == ZoneInfo("Europe/Zurich") 9 | assert SIXExchangeCalendar().name == "SIX" 10 | 11 | 12 | def test_2018_holidays(): 13 | # good friday: 2018-04-14 14 | # May 1st: 2018-05-01 15 | # christmas (observed): 2018-12-25 16 | # new years (observed): on a weekend, not rolled forward 17 | # https://www.six-group.com/exchanges/download/participants/regulation/trading_guides/trading_calendar_2018.pdf 18 | six = SIXExchangeCalendar() 19 | good_dates = six.valid_days("2018-01-01", "2018-12-31", tz="Europe/Zurich") 20 | 21 | for date in ["2018-05-24", "2018-06-15", "2018-03-23", "2018-12-21", "2018-12-27"]: 22 | assert pd.Timestamp(date, tz="Europe/Berlin") in good_dates 23 | for date in [ 24 | "2018-01-01", 25 | "2018-01-02", 26 | "2018-03-30", 27 | "2018-04-02", 28 | "2018-05-01", 29 | "2018-05-10", 30 | "2018-05-21", 31 | "2018-08-01", 32 | "2018-12-24", 33 | "2018-12-25", 34 | "2018-12-26", 35 | "2018-12-31", 36 | ]: 37 | assert pd.Timestamp(date, tz="Europe/Zurich") not in good_dates 38 | 39 | 40 | def test_eve_day_weekend(): 41 | # christmas eve (observed): on a weekend 42 | # christmas (observed): 2017-12-25 43 | # boxing day (observed): 2017-12-26 44 | # https://www.six-group.com/exchanges/download/participants/regulation/trading_guides/trading_calendar_2017.pdf 45 | six = SIXExchangeCalendar() 46 | good_dates = six.valid_days("2017-12-01", "2017-12-31", tz="Europe/Zurich") 47 | 48 | for date in ["2017-12-22", "2017-12-27"]: 49 | assert pd.Timestamp(date, tz="Europe/Berlin") in good_dates 50 | for date in ["2017-12-24", "2017-12-25", "2017-12-26"]: 51 | assert pd.Timestamp(date, tz="Europe/Zurich") not in good_dates 52 | 53 | 54 | def test_christmas_weekend(): 55 | # christmas eve (observed): on a weekend 56 | # christmas (observed): on a weekend 57 | # boxing day (observed): 2016-12-26 58 | # https://www.six-group.com/exchanges/download/participants/regulation/trading_guides/trading_calendar_2016.pdf 59 | six = SIXExchangeCalendar() 60 | good_dates = six.valid_days("2016-12-01", "2016-12-31", tz="Europe/Zurich") 61 | 62 | for date in ["2016-12-22", "2016-12-23", "2016-12-27"]: 63 | assert pd.Timestamp(date, tz="Europe/Berlin") in good_dates 64 | for date in ["2016-12-24", "2016-12-25", "2016-12-26"]: 65 | assert pd.Timestamp(date, tz="Europe/Zurich") not in good_dates 66 | 67 | 68 | def test_boxing_day_weekend(): 69 | # christmas eve (observed): 2020-12-24 70 | # christmas (observed): 2020-12-25 71 | # boxing day (observed): on a weekend, not rolled forward 72 | # https://www.six-group.com/exchanges/download/participants/regulation/trading_guides/trading_calendar_2020.pdf 73 | six = SIXExchangeCalendar() 74 | good_dates = six.valid_days("2020-12-01", "2020-12-31", tz="Europe/Zurich") 75 | 76 | for date in ["2020-12-22", "2020-12-22", "2020-12-28"]: 77 | assert pd.Timestamp(date, tz="Europe/Berlin") in good_dates 78 | for date in ["2020-12-24", "2020-12-25", "2020-12-26", "2020-12-27"]: 79 | assert pd.Timestamp(date, tz="Europe/Zurich") not in good_dates 80 | -------------------------------------------------------------------------------- /tests/test_sse_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pandas as pd 4 | from zoneinfo import ZoneInfo 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 | -------------------------------------------------------------------------------- /tests/test_tsx_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from zoneinfo import ZoneInfo 3 | from pandas.testing import assert_index_equal 4 | 5 | from pandas_market_calendars.calendars.tsx import TSXExchangeCalendar, VictoriaDay 6 | 7 | 8 | def test_time_zone(): 9 | assert TSXExchangeCalendar().tz == ZoneInfo("Canada/Eastern") 10 | assert TSXExchangeCalendar().name == "TSX" 11 | 12 | 13 | def test_victoria_day(): 14 | actual = VictoriaDay.dates("2009-01-01", "2020-12-31") 15 | 16 | expected = pd.DatetimeIndex( 17 | [ 18 | pd.Timestamp("2009-05-18"), 19 | pd.Timestamp("2010-05-24"), 20 | pd.Timestamp("2011-05-23"), 21 | pd.Timestamp("2012-05-21"), 22 | pd.Timestamp("2013-05-20"), 23 | pd.Timestamp("2014-05-19"), 24 | pd.Timestamp("2015-05-18"), 25 | pd.Timestamp("2016-05-23"), 26 | pd.Timestamp("2017-05-22"), 27 | pd.Timestamp("2018-05-21"), 28 | pd.Timestamp("2019-05-20"), 29 | pd.Timestamp("2020-05-18"), 30 | ] 31 | ) 32 | 33 | assert_index_equal(actual, expected) 34 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------